<?php

/*
 * This file is part of the DataGridBundle.
 *
 * (c) Abhoryo <abhoryo@free.fr>
 * (c) Stanislav Turza
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace APY\DataGridBundle\Grid\Column;

use APY\DataGridBundle\Grid\Filter;
use APY\DataGridBundle\Grid\Row;
use Doctrine\Common\Version as DoctrineVersion;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

abstract class Column
{
    const DEFAULT_VALUE = null;

    /**
     * Filter.
     */
    const DATA_CONJUNCTION = 0;
    const DATA_DISJUNCTION = 1;

    const OPERATOR_EQ = 'eq';
    const OPERATOR_NEQ = 'neq';
    const OPERATOR_LT = 'lt';
    const OPERATOR_LTE = 'lte';
    const OPERATOR_GT = 'gt';
    const OPERATOR_GTE = 'gte';
    const OPERATOR_BTW = 'btw';
    const OPERATOR_BTWE = 'btwe';
    const OPERATOR_LIKE = 'like';
    const OPERATOR_NLIKE = 'nlike';
    const OPERATOR_RLIKE = 'rlike';
    const OPERATOR_LLIKE = 'llike';
    const OPERATOR_SLIKE = 'slike'; //simple/strict LIKE
    const OPERATOR_NSLIKE = 'nslike';
    const OPERATOR_RSLIKE = 'rslike';
    const OPERATOR_LSLIKE = 'lslike';

    const OPERATOR_ISNULL = 'isNull';
    const OPERATOR_ISNOTNULL = 'isNotNull';

    protected static $availableOperators = [
        self::OPERATOR_EQ,
        self::OPERATOR_NEQ,
        self::OPERATOR_LT,
        self::OPERATOR_LTE,
        self::OPERATOR_GT,
        self::OPERATOR_GTE,
        self::OPERATOR_BTW,
        self::OPERATOR_BTWE,
        self::OPERATOR_LIKE,
        self::OPERATOR_NLIKE,
        self::OPERATOR_RLIKE,
        self::OPERATOR_LLIKE,
        self::OPERATOR_SLIKE,
        self::OPERATOR_NSLIKE,
        self::OPERATOR_RSLIKE,
        self::OPERATOR_LSLIKE,
        self::OPERATOR_ISNULL,
        self::OPERATOR_ISNOTNULL,
    ];

    /**
     * Align.
     */
    const ALIGN_LEFT = 'left';
    const ALIGN_RIGHT = 'right';
    const ALIGN_CENTER = 'center';

    protected static $aligns = [
        self::ALIGN_LEFT,
        self::ALIGN_RIGHT,
        self::ALIGN_CENTER,
    ];

    /**
     * Internal parameters.
     */
    protected $id;
    protected $title;
    protected $sortable;
    protected $filterable;
    protected $visible;
    protected $callback;
    protected $order;
    protected $size;
    protected $visibleForSource;
    protected $primary;
    protected $align;
    protected $inputType;
    protected $field;
    protected $role;
    protected $filterType;
    protected $params;
    protected $isSorted = false;
    protected $orderUrl;
    protected $authorizationChecker;
    protected $data;
    protected $operatorsVisible;
    protected $operators;
    protected $defaultOperator;
    protected $values = [];
    protected $selectFrom;
    protected $selectMulti;
    protected $selectExpanded;
    protected $searchOnClick = false;
    protected $safe;
    protected $separator;
    protected $joinType;
    protected $export;
    protected $class;
    protected $isManualField;
    protected $isAggregate;
    protected $usePrefixTitle;
    protected $translationDomain;

    protected $dataJunction = self::DATA_CONJUNCTION;

    /**
     * Default Column constructor.
     *
     * @param array $params
     */
    public function __construct($params = null)
    {
        $this->__initialize((array) $params);
    }

    public function __initialize(array $params)
    {
        $this->params = $params;
        $this->setId($this->getParam('id'));
        $this->setTitle($this->getParam('title', $this->getParam('field')));
        $this->setSortable($this->getParam('sortable', true));
        $this->setVisible($this->getParam('visible', true));
        $this->setSize($this->getParam('size', -1));
        $this->setFilterable($this->getParam('filterable', true));
        $this->setVisibleForSource($this->getParam('source', false));
        $this->setPrimary($this->getParam('primary', false));
        $this->setAlign($this->getParam('align', self::ALIGN_LEFT));
        $this->setInputType($this->getParam('inputType', 'text'));
        $this->setField($this->getParam('field'));
        $this->setRole($this->getParam('role'));
        $this->setOrder($this->getParam('order'));
        $this->setJoinType($this->getParam('joinType'));
        $this->setFilterType($this->getParam('filter', 'input'));
        $this->setSelectFrom($this->getParam('selectFrom', 'query'));
        $this->setValues($this->getParam('values', []));
        $this->setOperatorsVisible($this->getParam('operatorsVisible', true));
        $this->setIsManualField($this->getParam('isManualField', false));
        $this->setIsAggregate($this->getParam('isAggregate', false));
        $this->setUsePrefixTitle($this->getParam('usePrefixTitle', true));

        // Order is important for the order display
        $this->setOperators($this->getParam('operators', [
            self::OPERATOR_EQ,
            self::OPERATOR_NEQ,
            self::OPERATOR_LT,
            self::OPERATOR_LTE,
            self::OPERATOR_GT,
            self::OPERATOR_GTE,
            self::OPERATOR_BTW,
            self::OPERATOR_BTWE,
            self::OPERATOR_LIKE,
            self::OPERATOR_NLIKE,
            self::OPERATOR_RLIKE,
            self::OPERATOR_LLIKE,
            self::OPERATOR_SLIKE,
            self::OPERATOR_NSLIKE,
            self::OPERATOR_RSLIKE,
            self::OPERATOR_LSLIKE,
            self::OPERATOR_ISNULL,
            self::OPERATOR_ISNOTNULL,
        ]));
        $this->setDefaultOperator($this->getParam('defaultOperator', self::OPERATOR_LIKE));
        $this->setSelectMulti($this->getParam('selectMulti', false));
        $this->setSelectExpanded($this->getParam('selectExpanded', false));
        $this->setSearchOnClick($this->getParam('searchOnClick', false));
        $this->setSafe($this->getParam('safe', 'html'));
        $this->setSeparator($this->getParam('separator', '<br />'));
        $this->setExport($this->getParam('export'));
        $this->setClass($this->getParam('class'));
        $this->setTranslationDomain($this->getParam('translation_domain'));
    }

    protected function getParam($id, $default = null)
    {
        return isset($this->params[$id]) ? $this->params[$id] : $default;
    }

    /**
     * Draw cell.
     *
     * @param string $value
     * @param Row    $row
     * @param $router
     *
     * @return string
     */
    public function renderCell($value, $row, $router)
    {
        if (is_callable($this->callback)) {
            return call_user_func($this->callback, $value, $row, $router);
        }

        $value = is_bool($value) ? (int) $value : $value;
        if (array_key_exists((string) $value, $this->values)) {
            $value = $this->values[$value];
        }

        return $value;
    }

    /**
     * Set column callback.
     *
     * @param  $callback
     *
     * @return self
     */
    public function manipulateRenderCell($callback)
    {
        $this->callback = $callback;

        return $this;
    }

    /**
     * Set column identifier.
     *
     * @param $id
     *
     * @return self
     */
    public function setId($id)
    {
        $this->id = $id;

        return $this;
    }

    /**
     * get column identifier.
     *
     * @return int|string
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * get column render block identifier.
     *
     * @return int|string
     */
    public function getRenderBlockId()
    {
        // For Mapping fields and aggregate dql functions
        return str_replace(['.', ':'], '_', $this->id);
    }

    /**
     * Set column title.
     *
     * @param string $title
     *
     * @return \APY\DataGridBundle\Grid\Column\Column
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * Get column title.
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set column visibility.
     *
     * @param bool $visible
     *
     * @return $this
     */
    public function setVisible($visible)
    {
        $this->visible = $visible;

        return $this;
    }

    /**
     * Return column visibility.
     *
     * @return bool return true when column is visible
     */
    public function isVisible($isExported = false)
    {
        $visible = $isExported && $this->export !== null ? $this->export : $this->visible;

        if ($visible && $this->authorizationChecker !== null && $this->getRole() !== null) {
            return $this->authorizationChecker->isGranted($this->getRole());
        }

        return $visible;
    }

    /**
     * Return true if column is sorted.
     *
     * @return bool return true when column is sorted
     */
    public function isSorted()
    {
        return $this->isSorted;
    }

    public function setSortable($sortable)
    {
        $this->sortable = $sortable;

        return $this;
    }

    /**
     * column ability to sort.
     *
     * @return bool return true when column can be sorted
     */
    public function isSortable()
    {
        return $this->sortable;
    }

    /**
     * Return true if column is filtered.
     *
     * @return bool return true when column is filtered
     */
    public function isFiltered()
    {
        if ($this->hasFromOperandFilter()) {
            return true;
        }

        if ($this->hasToOperandFilter()) {
            return true;
        }

        return $this->hasOperatorFilter();
    }

    /**
     * @return bool
     */
    private function hasFromOperandFilter()
    {
        if (!isset($this->data['from'])) {
            return false;
        }

        if (!$this->isQueryValid($this->data['from'])) {
            return false;
        }

        return $this->data['from'] != static::DEFAULT_VALUE;
    }

    /**
     * @return bool
     */
    private function hasToOperandFilter()
    {
        if (!isset($this->data['to'])) {
            return false;
        }

        if (!$this->isQueryValid($this->data['to'])) {
            return false;
        }

        return $this->data['to'] != static::DEFAULT_VALUE;
    }

    /**
     * @return bool
     */
    private function hasOperatorFilter()
    {
        if (!isset($this->data['operator'])) {
            return false;
        }

        return $this->data['operator'] === self::OPERATOR_ISNULL || $this->data['operator'] === self::OPERATOR_ISNOTNULL;
    }

    /**
     * @param bool $filterable
     *
     * @return $this
     */
    public function setFilterable($filterable)
    {
        $this->filterable = $filterable;

        return $this;
    }

    /**
     * column ability to filter.
     *
     * @return bool return true when column can be filtred
     */
    public function isFilterable()
    {
        return $this->filterable;
    }

    /**
     * set column order.
     *
     * @param string $order asc|desc
     *
     * @return \APY\DataGridBundle\Grid\Column\Column
     */
    public function setOrder($order)
    {
        if ($order !== null) {
            $this->order = $order;
            $this->isSorted = true;
        }

        return $this;
    }

    /**
     * get column order.
     *
     * @return string asc|desc
     */
    public function getOrder()
    {
        return $this->order;
    }

    /**
     * Set column width.
     *
     * @param int $size in pixels
     *
     * @return \APY\DataGridBundle\Grid\Column\Column
     */
    public function setSize($size)
    {
        if ($size < -1) {
            throw new \InvalidArgumentException(sprintf('Unsupported column size %s, use positive value or -1 for auto resize', $size));
        }

        $this->size = $size;

        return $this;
    }

    /**
     * get column width.
     *
     * @return int column width in pixels
     */
    public function getSize()
    {
        return $this->size;
    }

    /**
     * set filter data from session | request.
     *
     * @param  $data
     *
     * @return \APY\DataGridBundle\Grid\Column\Column
     */
    public function setData($data)
    {
        $this->data = ['operator' => $this->getDefaultOperator(), 'from' => static::DEFAULT_VALUE, 'to' => static::DEFAULT_VALUE];

        $hasValue = false;
        if (isset($data['from']) && $this->isQueryValid($data['from'])) {
            $this->data['from'] = $data['from'];
            $hasValue = true;
        }

        if (isset($data['to']) && $this->isQueryValid($data['to'])) {
            $this->data['to'] = $data['to'];
            $hasValue = true;
        }

        $isNullOperator = (isset($data['operator']) && ($data['operator'] === self::OPERATOR_ISNULL || $data['operator'] === self::OPERATOR_ISNOTNULL));
        if (($hasValue || $isNullOperator) && isset($data['operator']) && $this->hasOperator($data['operator'])) {
            $this->data['operator'] = $data['operator'];
        }

        return $this;
    }

    /**
     * get filter data from session | request.
     *
     * @return array data
     */
    public function getData()
    {
        $result = [];

        $hasValue = false;
        if ($this->data['from'] != $this::DEFAULT_VALUE) {
            $result['from'] = $this->data['from'];
            $hasValue = true;
        }

        if ($this->data['to'] != $this::DEFAULT_VALUE) {
            $result['to'] = $this->data['to'];
            $hasValue = true;
        }

        $isNullOperator = (isset($this->data['operator']) && ($this->data['operator'] === self::OPERATOR_ISNULL || $this->data['operator'] === self::OPERATOR_ISNOTNULL));
        if ($hasValue || $isNullOperator) {
            $result['operator'] = $this->data['operator'];
        }

        return $result;
    }

    /**
     * Return true if filter value is correct (has to be overridden in each Column class that can be filtered, in order to catch wrong values).
     *
     * @return bool
     */
    public function isQueryValid($query)
    {
        return true;
    }

    /**
     * Set column visibility for source class.
     *
     * @param $visibleForSource
     *
     * @return \APY\DataGridBundle\Grid\Column\Column
     */
    public function setVisibleForSource($visibleForSource)
    {
        $this->visibleForSource = $visibleForSource;

        return $this;
    }

    /**
     * Return true is column in visible for source class.
     *
     * @return bool
     */
    public function isVisibleForSource()
    {
        return $this->visibleForSource;
    }

    /**
     * Set column as primary.
     *
     * @param bool $primary
     *
     * @return $this
     */
    public function setPrimary($primary)
    {
        $this->primary = $primary;

        return $this;
    }

    /**
     * Return true is column in primary.
     *
     * @return bool
     */
    public function isPrimary()
    {
        return $this->primary;
    }

    /**
     * Set column align.
     *
     * @param string $align left/right/center
     *
     * @return $this
     */
    public function setAlign($align)
    {
        if (!in_array($align, self::$aligns)) {
            throw new \InvalidArgumentException(sprintf('Unsupported align %s, just left, right and center are supported', $align));
        }

        $this->align = $align;

        return $this;
    }

    /**
     * get column align.
     *
     * @return bool
     */
    public function getAlign()
    {
        return $this->align;
    }

    public function setInputType($inputType)
    {
        $this->inputType = $inputType;

        return $this;
    }

    public function getInputType()
    {
        return $this->inputType;
    }

    public function setField($field)
    {
        $this->field = $field;

        return $this;
    }

    public function getField()
    {
        return $this->field;
    }

    public function setRole($role)
    {
        $this->role = $role;

        return $this;
    }

    public function getRole()
    {
        return $this->role;
    }

    /**
     * Filter.
     */
    public function setFilterType($filterType)
    {
        $this->filterType = strtolower($filterType);

        return  $this;
    }

    public function getFilterType()
    {
        return $this->filterType;
    }

    public function getFilters($source)
    {
        $filters = [];

        if ($this->hasOperator($this->data['operator'])) {
            if ($this instanceof ArrayColumn && in_array($this->data['operator'], [self::OPERATOR_EQ, self::OPERATOR_NEQ])) {
                $filters[] = new Filter($this->data['operator'], $this->data['from']);
            } else {
                switch ($this->data['operator']) {
                    case self::OPERATOR_BTW:
                        if ($this->data['from'] != static::DEFAULT_VALUE) {
                            $filters[] = new Filter(self::OPERATOR_GT, $this->data['from']);
                        }
                        if ($this->data['to'] != static::DEFAULT_VALUE) {
                            $filters[] = new Filter(self::OPERATOR_LT, $this->data['to']);
                        }
                        break;
                    case self::OPERATOR_BTWE:
                        if ($this->data['from'] != static::DEFAULT_VALUE) {
                            $filters[] = new Filter(self::OPERATOR_GTE, $this->data['from']);
                        }
                        if ($this->data['to'] != static::DEFAULT_VALUE) {
                            $filters[] = new Filter(self::OPERATOR_LTE, $this->data['to']);
                        }
                        break;
                    case self::OPERATOR_ISNULL:
                    case self::OPERATOR_ISNOTNULL:
                        $filters[] = new Filter($this->data['operator']);
                        break;
                    case self::OPERATOR_LIKE:
                    case self::OPERATOR_RLIKE:
                    case self::OPERATOR_LLIKE:
                    case self::OPERATOR_SLIKE:
                    case self::OPERATOR_RSLIKE:
                    case self::OPERATOR_LSLIKE:
                    case self::OPERATOR_EQ:
                        if ($this->getSelectMulti()) {
                            $this->setDataJunction(self::DATA_DISJUNCTION);
                        }
                    case self::OPERATOR_NEQ:
                    case self::OPERATOR_NLIKE:
                    case self::OPERATOR_NSLIKE:
                        foreach ((array) $this->data['from'] as $value) {
                            $filters[] = new Filter($this->data['operator'], $value);
                        }
                        break;
                    default:
                        $filters[] = new Filter($this->data['operator'], $this->data['from']);
                }
            }
        }

        return $filters;
    }

    public function setDataJunction($dataJunction)
    {
        $this->dataJunction = $dataJunction;

        return $this;
    }

    /**
     * get data filter junction (how column filters are connected with column data).
     *
     * @return bool self::DATA_CONJUNCTION | self::DATA_DISJUNCTION
     */
    public function getDataJunction()
    {
        return $this->dataJunction;
    }

    public function setOperators(array $operators)
    {
        $this->operators = $operators;

        return $this;
    }

    /**
     * Return column filter operators.
     *
     * @return array $operators
     */
    public function getOperators()
    {
        // Issue with Doctrine
        // -------------------
        // @see http://www.doctrine-project.org/jira/browse/DDC-1857
        // @see http://www.doctrine-project.org/jira/browse/DDC-1858
        if ($this->hasDQLFunction() && version_compare(DoctrineVersion::VERSION, '2.5') < 0) {
            return array_intersect($this->operators, [self::OPERATOR_EQ,
                self::OPERATOR_NEQ,
                self::OPERATOR_LT,
                self::OPERATOR_LTE,
                self::OPERATOR_GT,
                self::OPERATOR_GTE,
                self::OPERATOR_BTW,
                self::OPERATOR_BTWE, ]);
        }

        return $this->operators;
    }

    public function setDefaultOperator($defaultOperator)
    {
        // @todo: should this be \InvalidArgumentException?
        if (!$this->hasOperator($defaultOperator)) {
            throw new \Exception($defaultOperator . ' operator not found in operators list.');
        }

        $this->defaultOperator = $defaultOperator;

        return $this;
    }

    public function getDefaultOperator()
    {
        return $this->defaultOperator;
    }

    /**
     * Return true if $operator is in $operators.
     *
     * @param string $operator
     *
     * @return bool
     */
    public function hasOperator($operator)
    {
        return in_array($operator, $this->operators);
    }

    public function setOperatorsVisible($operatorsVisible)
    {
        $this->operatorsVisible = $operatorsVisible;

        return $this;
    }

    public function getOperatorsVisible()
    {
        return $this->operatorsVisible;
    }

    public function setValues(array $values)
    {
        $this->values = $values;

        return $this;
    }

    public function getValues()
    {
        return $this->values;
    }

    public function setSelectFrom($selectFrom)
    {
        $this->selectFrom = $selectFrom;

        return $this;
    }

    public function getSelectFrom()
    {
        return $this->selectFrom;
    }

    public function getSelectMulti()
    {
        return $this->selectMulti;
    }

    public function setSelectMulti($selectMulti)
    {
        $this->selectMulti = $selectMulti;
    }

    public function getSelectExpanded()
    {
        return $this->selectExpanded;
    }

    public function setSelectExpanded($selectExpanded)
    {
        $this->selectExpanded = $selectExpanded;
    }

    public function hasDQLFunction(&$matches = null)
    {
        $regex = '/(?P<all>(?P<field>\w+):(?P<function>\w+)(:)?(?P<parameters>\w*))$/';

        return ($matches === null) ? preg_match($regex, $this->field) : preg_match($regex, $this->field, $matches);
    }

    /**
     * Internal function.
     *
     * @param $authorizationChecker
     *
     * @return $this
     */
    public function setAuthorizationChecker(AuthorizationCheckerInterface $authorizationChecker)
    {
        $this->authorizationChecker = $authorizationChecker;

        return $this;
    }

    public function getParentType()
    {
        return '';
    }

    public function getType()
    {
        return '';
    }

    /**
     * By default all filers include a JavaScript onchange=submit block.  This
     * does not make sense in some cases, such as with multi-select filters.
     *
     * @todo Eventaully make this configurable via annotations?
     */
    public function isFilterSubmitOnChange()
    {
        return !$this->getSelectMulti();
    }

    public function setSearchOnClick($searchOnClick)
    {
        $this->searchOnClick = $searchOnClick;

        return $this;
    }

    public function getSearchOnClick()
    {
        return $this->searchOnClick;
    }

    /**
     * Allows to set twig escaping parameter (html, js, css, url, html_attr)
     * or to display raw value if type is false.
     *
     * @param string|bool $safeOption can be one of false, html, js, css, url, html_attr
     *
     * @return \APY\DataGridBundle\Grid\Column\Column
     */
    public function setSafe($safeOption)
    {
        $this->safe = $safeOption;

        return $this;
    }

    public function getSafe()
    {
        return $this->safe;
    }

    public function setSeparator($separator)
    {
        $this->separator = $separator;

        return $this;
    }

    public function getSeparator()
    {
        return $this->separator;
    }

    public function setJoinType($type)
    {
        $this->joinType = $type;

        return $this;
    }

    public function getJoinType()
    {
        return $this->joinType;
    }

    public function setExport($export)
    {
        $this->export = $export;

        return $this;
    }

    public function getExport()
    {
        return $this->export;
    }

    public function setClass($class)
    {
        $this->class = $class;

        return $this;
    }

    public function getClass()
    {
        return $this->class;
    }

    public function setIsManualField($isManualField)
    {
        $this->isManualField = $isManualField;
    }

    public function getIsManualField()
    {
        return $this->isManualField;
    }

    public function setIsAggregate($isAggregate)
    {
        $this->isAggregate = $isAggregate;
    }

    public function getIsAggregate()
    {
        return $this->isAggregate;
    }

    public function getUsePrefixTitle()
    {
        return $this->usePrefixTitle;
    }

    public function setUsePrefixTitle($usePrefixTitle)
    {
        $this->usePrefixTitle = $usePrefixTitle;

        return $this;
    }

    /**
     * Get TranslationDomain.
     *
     * @return string
     */
    public function getTranslationDomain()
    {
        return $this->translationDomain;
    }

    /**
     * Set TranslationDomain.
     *
     * @param string $translationDomain
     *
     * @return $this
     */
    public function setTranslationDomain($translationDomain)
    {
        $this->translationDomain = $translationDomain;

        return $this;
    }

    /**
     * @return array
     */
    public static function getAvailableOperators()
    {
        return self::$availableOperators;
    }
}


<?php

/*
 * This file is part of the DataGridBundle.
 *
 * (c) Abhoryo <abhoryo@free.fr>
 * (c) Stanislav Turza
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace APY\DataGridBundle\Grid;

use APY\DataGridBundle\Grid\Column\ActionsColumn;
use APY\DataGridBundle\Grid\Column\Column;
use APY\DataGridBundle\Grid\Helper\ColumnsIterator;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

class Columns implements \IteratorAggregate, \Countable
{
    protected $columns = [];
    protected $extensions = [];

    const MISSING_COLUMN_EX_MSG = 'Column with id "%s" doesn\'t exists';

    /**
     * @var AuthorizationCheckerInterface
     */
    protected $authorizationChecker;

    /**
     * @param AuthorizationCheckerInterface $authorizationChecker
     */
    public function __construct(AuthorizationCheckerInterface $authorizationChecker)
    {
        $this->authorizationChecker = $authorizationChecker;
    }

    /**
     * @param bool $showOnlySourceColumns
     *
     * @return ColumnsIterator
     */
    public function getIterator($showOnlySourceColumns = false)
    {
        return new ColumnsIterator(new \ArrayIterator($this->columns), $showOnlySourceColumns);
    }

    /**
     * Add column.
     *
     * @param Column $column
     * @param int    $position
     *
     * @return Columns
     */
    public function addColumn(Column $column, $position = 0)
    {
        $column->setAuthorizationChecker($this->authorizationChecker);

        if ($position == 0) {
            $this->columns[] = $column;
        } else {
            if ($position > 0) {
                --$position;
            } else {
                $position = max(0, count($this->columns) + $position);
            }

            $head = array_slice($this->columns, 0, $position);
            $tail = array_slice($this->columns, $position);
            $this->columns = array_merge($head, [$column], $tail);
        }

        return $this;
    }

    /**
     * @param $columnId
     *
     * @throws \InvalidArgumentException
     *
     * @return Column
     */
    public function getColumnById($columnId)
    {
        if (($column = $this->hasColumnById($columnId, true)) === false) {
            throw new \InvalidArgumentException(sprintf(self::MISSING_COLUMN_EX_MSG, $columnId));
        }

        return $column;
    }

    /**
     * @param $columnId
     * @param bool $returnColumn
     *
     * @return bool|Column|ActionsColumn
     */
    public function hasColumnById($columnId, $returnColumn = false)
    {
        foreach ($this->columns as $column) {
            if ($column->getId() == $columnId) {
                return $returnColumn ? $column : true;
            }
        }

        return false;
    }

    /**
     * @throws \InvalidArgumentException
     *
     * @return Column
     */
    public function getPrimaryColumn()
    {
        foreach ($this->columns as $column) {
            if ($column->isPrimary()) {
                return $column;
            }
        }

        throw new \InvalidArgumentException('Primary column doesn\'t exists');
    }

    /**
     * @return int
     */
    public function count()
    {
        return count($this->columns);
    }

    /**
     * @param $extension
     *
     * @return Columns
     */
    public function addExtension($extension)
    {
        $this->extensions[strtolower($extension->getType())] = $extension;

        return $this;
    }

    /**
     * @param $type
     *
     * @return bool
     */
    public function hasExtensionForColumnType($type)
    {
        return isset($this->extensions[$type]);
    }

    /**
     * @param $type
     *
     * @return mixed
     */
    public function getExtensionForColumnType($type)
    {
        // @todo: should not index be checked?
        return $this->extensions[$type];
    }

    /**
     * @return string
     */
    public function getHash()
    {
        $hash = '';
        foreach ($this->columns as $column) {
            $hash .= $column->getId();
        }

        return $hash;
    }

    /**
     * Sets order of Columns passing an array of column ids
     * If the list of ids is uncomplete, the remaining columns will be
     * placed after if keepOtherColumns is true.
     *
     * @param array $columnIds
     * @param bool  $keepOtherColumns
     *
     * @return Columns
     */
    public function setColumnsOrder(array $columnIds, $keepOtherColumns = true)
    {
        $reorderedColumns = [];
        $columnsIndexedByIds = [];

        foreach ($this->columns as $column) {
            $columnsIndexedByIds[$column->getId()] = $column;
        }

        foreach ($columnIds as $columnId) {
            if (isset($columnsIndexedByIds[$columnId])) {
                $reorderedColumns[] = $columnsIndexedByIds[$columnId];
                unset($columnsIndexedByIds[$columnId]);
            }
        }

        if ($keepOtherColumns) {
            $this->columns = array_merge($reorderedColumns, array_values($columnsIndexedByIds));
        } else {
            $this->columns = $reorderedColumns;
        }

        return $this;
    }
}


<?php

/*
 * This file is part of the DataGridBundle.
 *
 * (c) Abhoryo <abhoryo@free.fr>
 * (c) Stanislav Turza
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace APY\DataGridBundle\Grid;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;

class GridManager implements \IteratorAggregate, \Countable
{
    protected $container;

    protected $grids;

    protected $routeUrl = null;

    protected $exportGrid = null;

    protected $massActionGrid = null;

    const NO_GRID_EX_MSG = 'No grid has been added to the manager.';

    const SAME_GRID_HASH_EX_MSG = 'Some grids seem similar. Please set an Indentifier for your grids.';

    public function __construct($container)
    {
        $this->container = $container;
        $this->grids = new \SplObjectStorage();
    }

    public function getIterator()
    {
        return $this->grids;
    }

    public function count()
    {
        return $this->grids->count();
    }

    /**
     * @param mixed $id
     *
     * @return Grid
     */
    public function createGrid($id = null)
    {
        $grid = $this->container->get('grid');

        if ($id !== null) {
            $grid->setId($id);
        }

        $this->grids->attach($grid);

        return $grid;
    }

    public function isReadyForRedirect()
    {
        if ($this->grids->count() == 0) {
            throw new \RuntimeException(self::NO_GRID_EX_MSG);
        }

        $checkHash = [];

        $isReadyForRedirect = false;
        $this->grids->rewind();

        // Route url is the same for all grids
        if ($this->routeUrl === null) {
            $grid = $this->grids->current();
            $this->routeUrl = $grid->getRouteUrl();
        }

        while ($this->grids->valid()) {
            $grid = $this->grids->current();

            if ($grid->isReadyForRedirect()) {
                $isReadyForRedirect = true;
            }

            if (in_array($grid->getHash(), $checkHash)) {
                throw new \RuntimeException(self::SAME_GRID_HASH_EX_MSG);
            }

            $checkHash[] = $grid->getHash();

            $this->grids->next();
        }

        return $isReadyForRedirect;
    }

    public function isReadyForExport()
    {
        if ($this->grids->count() == 0) {
            throw new \RuntimeException(self::NO_GRID_EX_MSG);
        }

        $checkHash = [];

        $this->grids->rewind();
        while ($this->grids->valid()) {
            $grid = $this->grids->current();

            if (in_array($grid->getHash(), $checkHash)) {
                throw new \RuntimeException(self::SAME_GRID_HASH_EX_MSG);
            }

            $checkHash[] = $grid->getHash();

            if ($grid->isReadyForExport()) {
                $this->exportGrid = $grid;

                return true;
            }

            $this->grids->next();
        }

        return false;
    }

    public function isMassActionRedirect()
    {
        $this->grids->rewind();
        while ($this->grids->valid()) {
            $grid = $this->grids->current();

            if ($grid->isMassActionRedirect()) {
                $this->massActionGrid = $grid;

                return true;
            }

            $this->grids->next();
        }

        return false;
    }

    /**
     * Renders a view.
     *
     * @param string|array $param1   The view name or an array of parameters to pass to the view
     * @param string|array $param2   The view name or an array of parameters to pass to the view
     * @param Response     $response A response instance
     *
     * @return Response A Response instance
     */
    public function getGridManagerResponse($param1 = null, $param2 = null, Response $response = null)
    {
        $isReadyForRedirect = $this->isReadyForRedirect();

        if ($this->isReadyForExport()) {
            return $this->exportGrid->getExportResponse();
        }

        if ($this->isMassActionRedirect()) {
            return $this->massActionGrid->getMassActionResponse();
        }

        if ($isReadyForRedirect) {
            return new RedirectResponse($this->getRouteUrl());
        } else {
            if (is_array($param1) || $param1 === null) {
                $parameters = (array) $param1;
                $view = $param2;
            } else {
                $parameters = (array) $param2;
                $view = $param1;
            }

            $i = 1;
            $this->grids->rewind();
            while ($this->grids->valid()) {
                $parameters = array_merge(['grid' . $i => $this->grids->current()], $parameters);
                $this->grids->next();
                ++$i;
            }

            if ($view === null) {
                return $parameters;
            }

            return $this->container->get('templating')->renderResponse($view, $parameters, $response);
        }
    }

    public function getRouteUrl()
    {
        return $this->routeUrl;
    }

    public function setRouteUrl($routeUrl)
    {
        $this->routeUrl = $routeUrl;
    }
}


<?php

namespace APY\DataGridBundle\Grid\Type;

use APY\DataGridBundle\Grid\AbstractType;
use APY\DataGridBundle\Grid\GridBuilder;
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
 * Class for all grids type.
 *
 * @author  Quentin Ferrer
 */
class GridType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildGrid(GridBuilder $builder, array $options = [])
    {
        $builder
            ->setRoute($options['route'])
            ->setRouteParameters($options['route_parameters'])
            ->setPersistence($options['persistence'])
            ->setPage($options['page'])
            ->setMaxResults($options['max_results'])
            ->setMaxPerPage($options['max_per_page'])
            ->setFilterable($options['filterable'])
            ->setSortable($options['sortable'])
            ->setSortBy($options['sort_by'])
            ->setOrder($options['order'])
            ->setGroupBy($options['group_by']);

        if (!empty($options['source'])) {
            $builder->setSource($options['source']);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'source'           => null,
            'group_by'         => null,
            'sort_by'          => null,
            'order'            => 'asc',
            'page'             => 1,
            'route'            => '',
            'route_parameters' => [],
            'persistence'      => false,
            'max_per_page'     => 10,
            'max_results'      => null,
            'filterable'       => true,
            'sortable'         => true,
        ]);

        $allowedTypes = [
            'source'           => ['null', 'APY\DataGridBundle\Grid\Source\Source'],
            'group_by'         => ['null', 'string', 'array'],
            'route_parameters' => 'array',
            'persistence'      => 'bool',
            'filterable'       => 'bool',
            'sortable'         => 'bool',
        ];
        $allowedValues = [
            'order' => ['asc', 'desc'],
        ];
        if (method_exists($resolver, 'setDefault')) {
            // Symfony 2.6.0 and up
            foreach ($allowedTypes as $option => $types) {
                $resolver->setAllowedTypes($option, $types);
            }

            foreach ($allowedValues as $option => $values) {
                $resolver->setAllowedValues($option, $values);
            }
        } else {
            $resolver->setAllowedTypes($allowedTypes);
            $resolver->setAllowedValues($allowedValues);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'grid';
    }
}


<?php

/*
 * This file is part of the DataGridBundle.
 *
 * (c) Abhoryo <abhoryo@free.fr>
 * (c) Stanislav Turza
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace APY\DataGridBundle\Grid\Column;

use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer;

class DateTimeColumn extends Column
{
    protected $dateFormat = \IntlDateFormatter::MEDIUM;

    protected $timeFormat = \IntlDateFormatter::MEDIUM;

    protected $format;

    protected $fallbackFormat = 'Y-m-d H:i:s';

    protected $timezone;

    public function __initialize(array $params)
    {
        parent::__initialize($params);

        $this->setFormat($this->getParam('format'));
        $this->setOperators($this->getParam('operators', [
            self::OPERATOR_EQ,
            self::OPERATOR_NEQ,
            self::OPERATOR_LT,
            self::OPERATOR_LTE,
            self::OPERATOR_GT,
            self::OPERATOR_GTE,
            self::OPERATOR_BTW,
            self::OPERATOR_BTWE,
            self::OPERATOR_ISNULL,
            self::OPERATOR_ISNOTNULL,
        ]));
        $this->setDefaultOperator($this->getParam('defaultOperator', self::OPERATOR_EQ));
        $this->setTimezone($this->getParam('timezone', date_default_timezone_get()));
    }

    public function isQueryValid($query)
    {
        $result = array_filter((array) $query, [$this, 'isDateTime']);

        return !empty($result);
    }

    protected function isDateTime($query)
    {
        return strtotime($query) !== false;
    }

    public function getFilters($source)
    {
        $parentFilters = parent::getFilters($source);

        $filters = [];
        foreach ($parentFilters as $filter) {
            $filters[] = ($filter->getValue() === null) ? $filter : $filter->setValue(new \DateTime($filter->getValue()));
        }

        return $filters;
    }

    public function renderCell($value, $row, $router)
    {
        $value = $this->getDisplayedValue($value);

        if (is_callable($this->callback)) {
            $value = call_user_func($this->callback, $value, $row, $router);
        }

        return $value;
    }

    public function getDisplayedValue($value)
    {
        if (!empty($value)) {
            $dateTime = $this->getDatetime($value, new \DateTimeZone($this->getTimezone()));

            if (isset($this->format)) {
                $value = $dateTime->format($this->format);
            } else {
                try {
                    $transformer = new DateTimeToLocalizedStringTransformer(null, $this->getTimezone(), $this->dateFormat, $this->timeFormat);
                    $value = $transformer->transform($dateTime);
                } catch (\Exception $e) {
                    $value = $dateTime->format($this->fallbackFormat);
                }
            }

            if (array_key_exists((string) $value, $this->values)) {
                $value = $this->values[$value];
            }

            return $value;
        }

        return '';
    }

    /**
     * DateTimeHelper::getDatetime() from SonataIntlBundle.
     *
     * @param \Datetime|\DateTimeImmutable|string|int $data
     * @param \DateTimeZone timezone
     *
     * @return \Datetime
     */
    protected function getDatetime($data, \DateTimeZone $timezone)
    {
        if ($data instanceof \DateTime || $data instanceof \DateTimeImmutable) {
            return $data->setTimezone($timezone);
        }

        // the format method accept array or integer
        if (is_numeric($data)) {
            $data = (int) $data;
        }

        if (is_string($data)) {
            $data = strtotime($data);
        }

        // MongoDB Date and Timestamp
        if ($data instanceof \MongoDate || $data instanceof \MongoTimestamp) {
            $data = $data->sec;
        }

        // Mongodb bug ? timestamp value is on the key 'i' instead of the key 't'
        if (is_array($data) && array_keys($data) == ['t', 'i']) {
            $data = $data['i'];
        }

        $date = new \DateTime();
        $date->setTimestamp($data);
        $date->setTimezone($timezone);

        return $date;
    }

    public function setFormat($format)
    {
        $this->format = $format;

        return $this;
    }

    public function getFormat()
    {
        return $this->format;
    }

    public function getTimezone()
    {
        return $this->timezone;
    }

    public function setTimezone($timezone)
    {
        $this->timezone = $timezone;
    }

    public function getType()
    {
        return 'datetime';
    }
}


<?php

/*
 * This file is part of the DataGridBundle.
 *
 * (c) Abhoryo <abhoryo@free.fr>
 * (c) Stanislav Turza
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace APY\DataGridBundle\Grid\Action;

use APY\DataGridBundle\Grid\Row;

class RowAction implements RowActionInterface
{
    /** @var string */
    protected $title;

    /** @var string */
    protected $route;

    /** @var bool */
    protected $confirm;

    /** @var string */
    protected $confirmMessage;

    /** @var string */
    protected $target;

    /** @var string */
    protected $column = '__actions';

    /** @var array */
    protected $routeParameters = [];

    /** @var array */
    protected $routeParametersMapping = [];

    /** @var array */
    protected $attributes = [];

    /** @var string|null */
    protected $role;

    /** @var array */
    protected $callbacks = [];

    /** @var bool */
    protected $enabled = true;

    /**
     * Default RowAction constructor.
     *
     * @param string $title      Title of the row action
     * @param string $route      Route to the row action
     * @param bool   $confirm    Show confirm message if true
     * @param string $target     Set the target of this action (_self,_blank,_parent,_top)
     * @param array  $attributes Attributes of the anchor tag
     * @param string $role       Security role
     *
     * @return \APY\DataGridBundle\Grid\Action\RowAction
     */
    public function __construct($title, $route, $confirm = false, $target = '_self', $attributes = [], $role = null)
    {
        $this->title = $title;
        $this->route = $route;
        $this->confirm = $confirm;
        $this->confirmMessage = 'Do you want to ' . strtolower($title) . ' this row?';
        $this->target = $target;
        $this->attributes = $attributes;
        $this->role = $role;
    }

    // @todo: has this setter real sense? we passed this value from constructor
    /**
     * Set action title.
     *
     * @param string $title
     *
     * @return self
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getTitle()
    {
        return $this->title;
    }

    // @todo: has this setter real sense? we passed this value from constructor
    /**
     * Set action route.
     *
     * @param string $route
     *
     * @return self
     */
    public function setRoute($route)
    {
        $this->route = $route;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getRoute()
    {
        return $this->route;
    }

    // @todo: we should change this to something like "enableConfirm" as "false" is the default value and has pretty much
    // nosense to use setConfirm with false parameter.
    /**
     * Set action confirm.
     *
     * @param bool $confirm
     *
     * @return self
     */
    public function setConfirm($confirm)
    {
        $this->confirm = $confirm;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getConfirm()
    {
        return $this->confirm;
    }

    /**
     * Set action confirmMessage.
     *
     * @param string $confirmMessage
     *
     * @return self
     */
    public function setConfirmMessage($confirmMessage)
    {
        $this->confirmMessage = $confirmMessage;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getConfirmMessage()
    {
        return $this->confirmMessage;
    }

    /**
     * Set action target.
     *
     * @param string $target
     *
     * @return self
     */
    public function setTarget($target)
    {
        $this->target = $target;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getTarget()
    {
        return $this->target;
    }

    /**
     * Set action column.
     *
     * @param string $column Identifier of the action column
     *
     * @return self
     */
    public function setColumn($column)
    {
        $this->column = $column;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getColumn()
    {
        return $this->column;
    }

    /**
     * Add route parameter.
     *
     * @param array|string $routeParameters
     *
     * @return self
     */
    public function addRouteParameters($routeParameters)
    {
        $routeParameters = (array) $routeParameters;

        foreach ($routeParameters as $key => $routeParameter) {
            if (is_int($key)) {
                $this->routeParameters[] = $routeParameter;
            } else {
                $this->routeParameters[$key] = $routeParameter;
            }
        }

        return $this;
    }

    /**
     * Set route parameters.
     *
     * @param array|string $routeParameters
     *
     * @return self
     */
    public function setRouteParameters($routeParameters)
    {
        $this->routeParameters = (array) $routeParameters;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getRouteParameters()
    {
        return $this->routeParameters;
    }

    // @todo: why is this accepting string? it seems pretty useless, isn't it?
    /**
     * Set route parameters mapping.
     *
     * @param array|string $routeParametersMapping
     *
     * @return self
     */
    public function setRouteParametersMapping($routeParametersMapping)
    {
        $this->routeParametersMapping = (array) $routeParametersMapping;

        return $this;
    }

    /**
     * Map the parameter.
     *
     * @param string $name parameter
     *
     * @return null|string
     */
    public function getRouteParametersMapping($name)
    {
        return isset($this->routeParametersMapping[$name]) ? $this->routeParametersMapping[$name] : null;
    }

    /**
     * Set attributes.
     *
     * @param array $attributes
     *
     * @return self
     */
    public function setAttributes(array $attributes)
    {
        $this->attributes = $attributes;

        return $this;
    }

    /**
     * Add attribute.
     *
     * @param string $name
     * @param string $value
     *
     * @return self
     */
    public function addAttribute($name, $value)
    {
        $this->attributes[$name] = $value;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getAttributes()
    {
        return $this->attributes;
    }

    /**
     * set role.
     *
     * @param string $role
     *
     * @return self
     */
    public function setRole($role)
    {
        $this->role = $role;

        return $this;
    }

    /**
     * Get role.
     *
     * @return string
     */
    public function getRole()
    {
        return $this->role;
    }

    /**
     * Set render callback.
     *
     * @deprecated This is deprecated and will be removed in 3.0; use addManipulateRender instead.
     *
     * @param \Closure $callback
     *
     * @return self
     */
    public function manipulateRender(\Closure $callback)
    {
        return $this->addManipulateRender($callback);
    }

    /**
     * Add a callback to render callback stack.
     *
     * @param \Closure $callback
     *
     * @return self
     */
    public function addManipulateRender($callback)
    {
        $this->callbacks[] = $callback;

        return $this;
    }

    /**
     * Render action for row.
     *
     * @param Row $row
     *
     * @return RowAction|null
     */
    public function render($row)
    {
        foreach ($this->callbacks as $callback) {
            if (is_callable($callback)) {
                if (null === call_user_func($callback, $this, $row)) {
                    return;
                }
            }
        }

        return $this;
    }

    // @todo: should not this be "isEnabled"?
    /**
     * {@inheritdoc}
     */
    public function getEnabled()
    {
        return $this->enabled;
    }

    // @todo: should not this be "enable" as default value is false?
    /**
     * Set the enabled state of this action.
     *
     * @param bool $enabled
     *
     * @return self
     */
    public function setEnabled($enabled)
    {
        $this->enabled = $enabled;

        return $this;
    }
}


<?php

namespace APY\DataGridBundle\Grid;

use APY\DataGridBundle\Grid\Column\Column;
use APY\DataGridBundle\Grid\Exception\ColumnAlreadyExistsException;
use APY\DataGridBundle\Grid\Exception\ColumnNotFoundException;
use APY\DataGridBundle\Grid\Exception\TypeAlreadyExistsException;
use APY\DataGridBundle\Grid\Exception\TypeNotFoundException;

/**
 * The central registry of the Grid component.
 *
 * @author  Quentin Ferrer
 */
class GridRegistry implements GridRegistryInterface
{
    /**
     * List of types.
     *
     * @var GridTypeInterface[]
     */
    private $types = [];

    /**
     * List of columns.
     *
     * @var Column[]
     */
    private $columns = [];

    /**
     * Add a grid type.
     *
     * @param GridTypeInterface $type
     *
     * @return $this
     */
    public function addType(GridTypeInterface $type)
    {
        $name = $type->getName();

        if ($this->hasType($name)) {
            throw new TypeAlreadyExistsException($name);
        }

        $this->types[$name] = $type;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getType($name)
    {
        if (!$this->hasType($name)) {
            throw new TypeNotFoundException($name);
        }

        $type = $this->types[$name];

        return $type;
    }

    /**
     * {@inheritdoc}
     */
    public function hasType($name)
    {
        if (isset($this->types[$name])) {
            return true;
        }

        return false;
    }

    /**
     * Add a column type.
     *
     * @param Column $column
     *
     * @return $this
     */
    public function addColumn(Column $column)
    {
        $type = $column->getType();

        if ($this->hasColumn($type)) {
            throw new ColumnAlreadyExistsException($type);
        }

        $this->columns[$type] = $column;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getColumn($type)
    {
        if (!$this->hasColumn($type)) {
            throw new ColumnNotFoundException($type);
        }

        $column = $this->columns[$type];

        return $column;
    }

    /**
     * {@inheritdoc}
     */
    public function hasColumn($type)
    {
        if (isset($this->columns[$type])) {
            return true;
        }

        return false;
    }
}


<?php

/*
 * This file is part of the DataGridBundle.
 *
 * (c) Abhoryo <abhoryo@free.fr>
 * (c) Stanislav Turza
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace APY\DataGridBundle\Grid\Export;

use APY\DataGridBundle\Grid\Column\ArrayColumn;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;

abstract class Export implements ExportInterface, ContainerAwareInterface
{
    const DEFAULT_TEMPLATE = 'APYDataGridBundle::blocks.html.twig';

    protected $title;

    protected $fileName;

    protected $fileExtension = null;

    protected $mimeType = 'application/octet-stream';

    protected $parameters = [];

    protected $container;

    protected $templates;

    protected $twig;

    protected $grid;

    protected $params = [];

    protected $content;

    protected $charset;

    protected $role;

    /**
     * Default Export constructor.
     *
     * @param string $title    Title of the export
     * @param string $fileName FileName of the export
     * @param array  $params   Additionnal parameters for the export
     * @param string $charset  Charset of the exported data
     * @param string $role     Security role
     *
     * @return \APY\DataGridBundle\Grid\Export\Export
     */
    public function __construct($title, $fileName = 'export', $params = [], $charset = 'UTF-8', $role = null)
    {
        $this->title = $title;
        $this->fileName = $fileName;
        $this->params = $params;
        $this->charset = $charset;
        $this->role = $role;
    }

    /**
     * Sets the Container associated with this Controller.
     *
     * @param ContainerInterface $container A ContainerInterface instance
     *
     * @return \APY\DataGridBundle\Grid\Export\Export
     */
    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;

        $this->twig = $this->container->get('twig');

        return $this;
    }

    /**
     * gets the Container associated with this Controller.
     *
     * @return ContainerInterface
     */
    public function getContainer()
    {
        return $this->container;
    }

    /**
     * gets the export Response.
     *
     * @return Response
     */
    public function getResponse()
    {
        // Response
        $kernelCharset = $this->container->getParameter('kernel.charset');
        if ($this->charset != $kernelCharset && function_exists('mb_strlen')) {
            $this->content = mb_convert_encoding($this->content, $this->charset, $kernelCharset);
            $filesize = mb_strlen($this->content, '8bit');
        } else {
            $filesize = strlen($this->content);
            $this->charset = $kernelCharset;
        }

        $headers = [
            'Content-Description'       => 'File Transfer',
            'Content-Type'              => $this->getMimeType(),
            'Content-Disposition'       => sprintf('attachment; filename="%s"', $this->getBaseName()),
            'Content-Transfer-Encoding' => 'binary',
            'Cache-Control'             => 'must-revalidate',
            'Pragma'                    => 'public',
            'Content-Length'            => $filesize,
        ];

        $response = new Response($this->content, 200, $headers);
        $response->setCharset($this->charset);
        $response->expire();

        return $response;
    }

    /**
     * sets the Content of the export.
     *
     * @param string $content
     *
     * @return self
     */
    public function setContent($content = '')
    {
        $this->content = $content;

        return $this;
    }

    /**
     * gets the Content of the export.
     *
     * @return string
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * Get data form the grid.
     *
     * @param Grid $grid
     *
     * @return array
     *
     * array(
     *     'titles' => array(
     *         'column_id_1' => 'column_title_1',
     *         'column_id_2' => 'column_title_2'
     *     ),
     *     'rows' =>array(
     *          array(
     *              'column_id_1' => 'cell_value_1_1',
     *              'column_id_2' => 'cell_value_1_2'
     *          ),
     *          array(
     *              'column_id_1' => 'cell_value_2_1',
     *              'column_id_2' => 'cell_value_2_2'
     *          )
     *     )
     * )
     */
    protected function getGridData($grid)
    {
        $result = [];

        $this->grid = $grid;

        if ($this->grid->isTitleSectionVisible()) {
            $result['titles'] = $this->getGridTitles();
        }

        $result['rows'] = $this->getGridRows();

        return $result;
    }

    protected function getRawGridData($grid)
    {
        $result = [];
        $this->grid = $grid;

        if ($this->grid->isTitleSectionVisible()) {
            $result['titles'] = $this->getRawGridTitles();
        }

        $result['rows'] = $this->getRawGridRows();

        return $result;
    }

    /**
     * Get data form the grid in a flat array.
     *
     * @param Grid $grid
     *
     * @return array
     *
     * array(
     *     '0' => array(
     *         'column_id_1' => 'column_title_1',
     *         'column_id_2' => 'column_title_2'
     *     ),
     *     '1' => array(
     *          'column_id_1' => 'cell_value_1_1',
     *          'column_id_2' => 'cell_value_1_2'
     *      ),
     *     '2' => array(
     *          'column_id_1' => 'cell_value_2_1',
     *          'column_id_2' => 'cell_value_2_2'
     *      )
     * )
     */
    protected function getFlatGridData($grid)
    {
        $data = $this->getGridData($grid);

        $flatData = [];
        if (isset($data['titles'])) {
            $flatData[] = $data['titles'];
        }

        return array_merge($flatData, $data['rows']);
    }

    protected function getFlatRawGridData($grid)
    {
        $data = $this->getRawGridData($grid);

        $flatData = [];
        if (isset($data['titles'])) {
            $flatData[] = $data['titles'];
        }

        return array_merge($flatData, $data['rows']);
    }

    protected function getGridTitles()
    {
        $titlesHTML = $this->renderBlock('grid_titles', ['grid' => $this->grid]);

        preg_match_all('#]*?>(.*)?	#isU', $titlesHTML, $matches);

        if (empty($matches)) {
            preg_match_all('#]*?>(.*)?	#isU', $titlesHTML, $matches);
        }

        if (empty($matches)) {
            new \Exception('Table header (th or td) tags not found.');
        }

        $titlesClean = array_map([$this, 'cleanHTML'], $matches[0]);

        $i = 0;
        $titles = [];

        foreach ($this->grid->getColumns() as $column) {
            if ($column->isVisible(true)) {
                if (!isset($titlesClean[$i])) {
                    throw new \OutOfBoundsException('There are more visible columns than titles found.');
                }
                $titles[$column->getId()] = $titlesClean[$i++];
            }
        }

        return $titles;
    }

    protected function getRawGridTitles()
    {
        $translator = $this->container->get('translator');

        $titles = [];
        foreach ($this->grid->getColumns() as $column) {
            if ($column->isVisible(true)) {
                $titles[] = utf8_decode($translator->trans(/* @Ignore */$column->getTitle()));
            }
        }

        return $titles;
    }

    protected function getGridRows()
    {
        $rows = [];
        foreach ($this->grid->getRows() as $i => $row) {
            foreach ($this->grid->getColumns() as $column) {
                if ($column->isVisible(true)) {
                    $cellHTML = $this->getGridCell($column, $row);
                    $rows[$i][$column->getId()] = $this->cleanHTML($cellHTML);
                }
            }
        }

        return $rows;
    }

    protected function getRawGridRows()
    {
        $rows = [];
        foreach ($this->grid->getRows() as $i => $row) {
            foreach ($this->grid->getColumns() as $column) {
                if ($column->isVisible(true)) {
                    $rows[$i][$column->getId()] = $row->getField($column->getId());
                }
            }
        }

        return $rows;
    }

    protected function getGridCell($column, $row)
    {
        $values = $row->getField($column->getId());

        // Cast a datetime won't work.
        if ($column instanceof ArrayColumn || !is_array($values)) {
            $values = [$values];
        }

        $separator = $column->getSeparator();

        $block = null;
        $return = [];
        foreach ($values as $sourceValue) {
            $value = $column->renderCell($sourceValue, $row, $this->container->get('router'));

            $id = $this->grid->getId();

            if (($id != '' && ($block !== null
                        || $this->hasBlock($block = 'grid_' . $id . '_column_' . $column->getRenderBlockId() . '_cell')
                        || $this->hasBlock($block = 'grid_' . $id . '_column_' . $column->getType() . '_cell')
                        || $this->hasBlock($block = 'grid_' . $id . '_column_' . $column->getParentType() . '_cell')))
                || $this->hasBlock($block = 'grid_' . $id . '_column_id_' . $column->getRenderBlockId() . '_cell')
                || $this->hasBlock($block = 'grid_' . $id . '_column_type_' . $column->getType() . '_cell')
                || $this->hasBlock($block = 'grid_' . $id . '_column_type_' . $column->getParentType() . '_cell')
                || $this->hasBlock($block = 'grid_column_' . $column->getRenderBlockId() . '_cell')
                || $this->hasBlock($block = 'grid_column_' . $column->getType() . '_cell')
                || $this->hasBlock($block = 'grid_column_' . $column->getParentType() . '_cell')
                || $this->hasBlock($block = 'grid_column_id_' . $column->getRenderBlockId() . '_cell')
                || $this->hasBlock($block = 'grid_column_type_' . $column->getType() . '_cell')
                || $this->hasBlock($block = 'grid_column_type_' . $column->getParentType() . '_cell')) {
                $html = $this->renderBlock($block, ['grid' => $this->grid, 'column' => $column, 'row' => $row, 'value' => $value, 'sourceValue' => $sourceValue]);
            } else {
                $html = $this->renderBlock('grid_column_cell', ['grid' => $this->grid, 'column' => $column, 'row' => $row, 'value' => $value, 'sourceValue' => $sourceValue]);
                $block = null;
            }

            // Fix blank separator. The
 will be removed by the HTML cleaner.
            if (false !== strpos($separator, 'br')) {
                $html = str_replace($separator, ',', $html);
            }

            $return[] = $html;
        }

        $value = implode($separator, $return);

        return $value;
    }

    /**
     * Has block.
     *
     * @param $name string
     *
     * @return bool
     */
    protected function hasBlock($name)
    {
        foreach ($this->getTemplates() as $template) {
            if ($template->hasBlock($name, [])) {
                return true;
            }
        }

        return false;
    }

    /**
     * Render block.
     *
     * @param $name string
     * @param $parameters string
     *
     * @return string
     */
    protected function renderBlock($name, $parameters)
    {
        foreach ($this->getTemplates() as $template) {
            if ($template->hasBlock($name, [])) {
                return $template->renderBlock($name, array_merge($parameters, $this->params));
            }
        }

        throw new \InvalidArgumentException(sprintf('Block "%s" doesn\'t exist in grid template "%s".', $name, 'ee'));
    }

    /**
     * Template Loader.
     *
     * @throws \Exception
     *
     * @return \Twig_TemplateInterface[]
     */
    protected function getTemplates()
    {
        if (empty($this->templates)) {
            $this->setTemplate($this->grid->getTemplate());
        }

        return $this->templates;
    }

    /**
     * set template.
     *
     * @param \Twig_TemplateInterface|string $template
     *
     * @throws \Exception
     *
     * @return \APY\DataGridBundle\Grid\Export\Export
     */
    public function setTemplate($template)
    {
        if (is_string($template)) {
            if (substr($template, 0, 8) === '__SELF__') {
                $this->templates = $this->getTemplatesFromString(substr($template, 8));
                $this->templates[] = $this->twig->loadTemplate(static::DEFAULT_TEMPLATE);
            } else {
                $this->templates = $this->getTemplatesFromString($template);
            }
        } elseif ($this->templates === null) {
            $this->templates[] = $this->twig->loadTemplate(static::DEFAULT_TEMPLATE);
        } else {
            throw new \Exception('Unable to load template');
        }

        return $this;
    }

    protected function getTemplatesFromString($theme)
    {
        $templates = [];

        $template = $this->twig->loadTemplate($theme);
        while ($template instanceof \Twig_Template) {
            $templates[] = $template;
            $template = $template->getParent([]);
        }

        return $templates;
    }

    protected function cleanHTML($value)
    {
        // Handle image
        $value = preg_replace('#]*title="([^"]*)"[^>]*?/>#isU', '\1', $value);

        // Clean indent
        $value = preg_replace('/>[\s\n\t\r]*</', '><', $value);

        // Clean HTML tags
        $value = strip_tags($value);

        // Convert Special Characters in HTML
        $value = html_entity_decode($value, ENT_QUOTES);

        // Remove whitespace
        $value = preg_replace('/\s\s+/', ' ', $value);

        // Fix space
        $value = preg_replace('/\s,/', ',', $value);

        // Trim
        $value = trim($value);

        return $value;
    }

    /**
     * set title.
     *
     * @param string $title
     *
     * @return self
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * get title.
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * set file name.
     *
     * @param string $fileName
     *
     * @return self
     */
    public function setFileName($fileName)
    {
        $this->fileName = $fileName;

        return $this;
    }

    /**
     * get file name.
     *
     * @return string
     */
    public function getFileName()
    {
        return $this->fileName;
    }

    /**
     * set file extension.
     *
     * @param string $fileExtension
     *
     * @return self
     */
    public function setFileExtension($fileExtension)
    {
        $this->fileExtension = $fileExtension;

        return $this;
    }

    /**
     * get file extension.
     *
     * @return string
     */
    public function getFileExtension()
    {
        return $this->fileExtension;
    }

    /**
     * get base name.
     *
     * @return string
     */
    public function getBaseName()
    {
        return $this->fileName . (isset($this->fileExtension) ? ".$this->fileExtension" : '');
    }

    /**
     * set response mime type.
     *
     * @param string $mimeType
     *
     * @return self
     */
    public function setMimeType($mimeType)
    {
        $this->mimeType = $mimeType;

        return $this;
    }

    /**
     * get response mime type.
     *
     * @return string
     */
    public function getMimeType()
    {
        return $this->mimeType;
    }

    /**
     * set response charset.
     *
     * @param string $charset
     *
     * @return self
     */
    public function setCharset($charset)
    {
        $this->charset = $charset;

        return $this;
    }

    /**
     * get response charset.
     *
     * @return string
     */
    public function getCharset()
    {
        return $this->charset;
    }

    /**
     * set parameters.
     *
     * @param array $parameters
     *
     * @return self
     */
    public function setParameters(array $parameters)
    {
        $this->parameters = $parameters;

        return $this;
    }

    /**
     * get parameters.
     *
     * @return array
     */
    public function getParameters()
    {
        return $this->parameters;
    }

    /**
     * has parameter.
     *
     * @return mixed
     */
    public function hasParameter($name)
    {
        return array_key_exists($name, $this->parameters);
    }

    /**
     * add parameter.
     *
     * @param $name
     * @param $value
     *
     * @return \APY\DataGridBundle\Grid\Export\Export
     */
    public function addParameter($name, $value)
    {
        $this->parameters[$name] = $value;

        return $this;
    }

    /**
     * get parameter.
     *
     * @return mixed
     */
    public function getParameter($name)
    {
        if (!$this->hasParameter($name)) {
            throw new \InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name));
        }

        return $this->parameters[$name];
    }

    /**
     * set role.
     *
     * @param mixed $role
     *
     * @return self
     */
    public function setRole($role)
    {
        $this->role = $role;

        return $this;
    }

    /**
     * Get role.
     *
     * @return mixed
     */
    public function getRole()
    {
        return $this->role;
    }
}


<?php

/*
 * This file is part of the DataGridBundle.
 *
 * (c) Abhoryo <abhoryo@free.fr>
 * (c) Stanislav Turza
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace APY\DataGridBundle\Grid\Column;

class ActionsColumn extends Column
{
    protected $rowActions;

     /**
     * ActionsColumn constructor.
     *
     * @param string $column     Identifier of the column
     * @param string $title      Title of the column
     * @param array  $rowActions Array of rowAction
     */
    public function __construct($column, $title, array $rowActions = [])
    {
        $this->rowActions = $rowActions;

        parent::__construct([
            'id'         => $column,
            'title'      => $title,
            'sortable'   => false,
            'source'     => false,
            'filterable' => true, // Show a reset link instead of a filter
        ]);
    }

    public function getRouteParameters($row, $action)
    {
        $actionParameters = $action->getRouteParameters();

        if (!empty($actionParameters)) {
            $routeParameters = [];

            foreach ($actionParameters as $name => $parameter) {
                if (is_int($name)) {
                    if (($name = $action->getRouteParametersMapping($parameter)) === null) {
                        $name = $this->getValidRouteParameters($parameter);
                    }
                    $routeParameters[$name] = $row->getField($parameter);
                } else {
                    $routeParameters[$this->getValidRouteParameters($name)] = $parameter;
                }
            }

            return $routeParameters;
        }

        return [$row->getPrimaryField() => $row->getPrimaryFieldValue()];
    }

    protected function getValidRouteParameters($name)
    {
        $pos = 0;
        while (($pos = strpos($name, '.', ++$pos)) !== false) {
            $name = substr($name, 0, $pos) . strtoupper(substr($name, $pos + 1, 1)) . substr($name, $pos + 2);
        }

        return $name;
    }

    public function getRowActions()
    {
        return $this->rowActions;
    }

    public function setRowActions(array $rowActions)
    {
        $this->rowActions = $rowActions;

        return $this;
    }

    public function isVisible($isExported = false)
    {
        if ($isExported) {
            return false;
        }

        return parent::isVisible();
    }

    public function getFilterType()
    {
        return $this->getType();
    }

    /**
     * Get the list of actions to render.
     *
     * @param $row
     *
     * @return array
     */
    public function getActionsToRender($row)
    {
        $list = $this->rowActions;
        foreach ($list as $i => $a) {
            $action = clone $a;
            $list[$i] = $action->render($row);
            if (null === $list[$i]) {
                unset($list[$i]);
            }
        }

        return $list;
    }

    public function getType()
    {
        return 'actions';
    }
}


<?php

namespace APY\DataGridBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

/**
 * Class TranslationPass.
 *
 * @author Quentin FERRER
 */
class TranslationPass implements CompilerPassInterface
{
    /**
     * {@inheritdoc}
     */
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('jms_translation.extractor.file_extractor')) {
            return;
        }

        $extractor = new Definition('APY\DataGridBundle\Translation\ColumnTitleAnnotationTranslationExtractor');
        $extractor
            ->setPublic(false)
            ->addTag('jms_translation.file_visitor');

        $container->setDefinition('grid.translation_extractor', $extractor);
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 *
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace Yiisoft\Rbac;

use yii\base\Component;
use Yiisoft\Rbac\Exceptions\InvalidArgumentException;
use Yiisoft\Rbac\Exceptions\InvalidConfigException;
use Yiisoft\Rbac\Exceptions\InvalidValueException;

/**
 * BaseManager is a base class implementing [[ManagerInterface]] for RBAC management.
 *
 * For more details and usage information on DbManager, see the [guide article on security authorization](guide:security-authorization).
 *
 * @property Role[] $defaultRoleInstances Default roles. The array is indexed by the role names. This property
 * is read-only.
 * @property string[] $defaultRoles Default roles. Note that the type of this property differs in getter and
 * setter. See [[getDefaultRoles()]] and [[setDefaultRoles()]] for details.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 *
 * @since 2.0
 */
abstract class BaseManager extends Component implements ManagerInterface
{
    /**
     * @var array a list of role names that are assigned to every user automatically without calling [[assign()]].
     *            Note that these roles are applied to users, regardless of their state of authentication.
     */
    protected $defaultRoles = [];

    /**
     * @var RuleFactoryInterface
     */
    protected $ruleFactory;

    /**
     * @param RuleFactoryInterface $ruleFactory
     */
    public function __construct(RuleFactoryInterface $ruleFactory)
    {
        $this->ruleFactory = $ruleFactory;
    }

    /**
     * Returns the named auth item.
     *
     * @param string $name the auth item name.
     *
     * @return Item the auth item corresponding to the specified name. Null is returned if no such item.
     */
    abstract protected function getItem($name);

    /**
     * Returns the items of the specified type.
     *
     * @param int $type the auth item type (either [[Item::TYPE_ROLE]] or [[Item::TYPE_PERMISSION]]
     *
     * @return Item[] the auth items of the specified type.
     */
    abstract protected function getItems($type);

    /**
     * Adds an auth item to the RBAC system.
     *
     * @param Item $item the item to add
     *
     * @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique)
     *
     * @return bool whether the auth item is successfully added to the system
     */
    abstract protected function addItem($item);

    /**
     * Adds a rule to the RBAC system.
     *
     * @param Rule $rule the rule to add
     *
     * @throws \Exception if data validation or saving fails (such as the name of the rule is not unique)
     *
     * @return bool whether the rule is successfully added to the system
     */
    abstract protected function addRule($rule);

    /**
     * Removes an auth item from the RBAC system.
     *
     * @param Item $item the item to remove
     *
     * @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique)
     *
     * @return bool whether the role or permission is successfully removed
     */
    abstract protected function removeItem($item);

    /**
     * Removes a rule from the RBAC system.
     *
     * @param Rule $rule the rule to remove
     *
     * @throws \Exception if data validation or saving fails (such as the name of the rule is not unique)
     *
     * @return bool whether the rule is successfully removed
     */
    abstract protected function removeRule($rule);

    /**
     * Updates an auth item in the RBAC system.
     *
     * @param string $name the name of the item being updated
     * @param Item   $item the updated item
     *
     * @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique)
     *
     * @return bool whether the auth item is successfully updated
     */
    abstract protected function updateItem($name, $item);

    /**
     * Updates a rule to the RBAC system.
     *
     * @param string $name the name of the rule being updated
     * @param Rule   $rule the updated rule
     *
     * @throws \Exception if data validation or saving fails (such as the name of the rule is not unique)
     *
     * @return bool whether the rule is successfully updated
     */
    abstract protected function updateRule($name, $rule);

    /**
     * {@inheritdoc}
     */
    public function createRole($name)
    {
        $role = new Role();
        $role->name = $name;

        return $role;
    }

    /**
     * {@inheritdoc}
     */
    public function createPermission($name)
    {
        $permission = new Permission();
        $permission->name = $name;

        return $permission;
    }

    /**
     * {@inheritdoc}
     */
    public function add($object)
    {
        if ($object instanceof Item) {
            if ($object->ruleName && $this->getRule($object->ruleName) === null) {
                $rule = $this->createRule($object->ruleName);
                $this->addRule($rule);
            }

            return $this->addItem($object);
        } elseif ($object instanceof Rule) {
            return $this->addRule($object);
        }

        throw new InvalidArgumentException('Adding unsupported object type.');
    }

    protected function createRule($name): Rule
    {
        return $this->ruleFactory->create($name)->setName($name);
    }

    /**
     * {@inheritdoc}
     */
    public function remove($object)
    {
        if ($object instanceof Item) {
            return $this->removeItem($object);
        } elseif ($object instanceof Rule) {
            return $this->removeRule($object);
        }

        throw new InvalidArgumentException('Removing unsupported object type.');
    }

    /**
     * {@inheritdoc}
     */
    public function update($name, $object)
    {
        if ($object instanceof Item) {
            if ($object->ruleName && $this->getRule($object->ruleName) === null) {
                $rule = $this->createRule($object->ruleName);
                $this->addRule($rule);
            }

            return $this->updateItem($name, $object);
        } elseif ($object instanceof Rule) {
            return $this->updateRule($name, $object);
        }

        throw new InvalidArgumentException('Updating unsupported object type.');
    }

    /**
     * {@inheritdoc}
     */
    public function getRole($name)
    {
        $item = $this->getItem($name);

        return $item instanceof Item && $item->type == Item::TYPE_ROLE ? $item : null;
    }

    /**
     * {@inheritdoc}
     */
    public function getPermission($name)
    {
        $item = $this->getItem($name);

        return $item instanceof Item && $item->type == Item::TYPE_PERMISSION ? $item : null;
    }

    /**
     * {@inheritdoc}
     */
    public function getRoles()
    {
        return $this->getItems(Item::TYPE_ROLE);
    }

    /**
     * Set default roles.
     *
     * @param string[]|\Closure $roles either array of roles or a callable returning it
     *
     * @throws InvalidArgumentException when $roles is neither array nor Closure
     * @throws InvalidValueException    when Closure return is not an array
     *
     * @since 2.0.14
     */
    public function setDefaultRoles($roles)
    {
        if (is_array($roles)) {
            $this->defaultRoles = $roles;
        } elseif ($roles instanceof \Closure) {
            $roles = call_user_func($roles);
            if (!is_array($roles)) {
                throw new InvalidValueException('Default roles closure must return an array');
            }
            $this->defaultRoles = $roles;
        } else {
            throw new InvalidArgumentException('Default roles must be either an array or a callable');
        }

        return $this;
    }

    /**
     * Get default roles.
     *
     * @return string[] default roles
     *
     * @since 2.0.14
     */
    public function getDefaultRoles()
    {
        return $this->defaultRoles;
    }

    /**
     * Returns defaultRoles as array of Role objects.
     *
     * @since 2.0.12
     *
     * @return Role[] default roles. The array is indexed by the role names
     */
    public function getDefaultRoleInstances()
    {
        $result = [];
        foreach ($this->defaultRoles as $roleName) {
            $result[$roleName] = $this->createRole($roleName);
        }

        return $result;
    }

    /**
     * {@inheritdoc}
     */
    public function getPermissions()
    {
        return $this->getItems(Item::TYPE_PERMISSION);
    }

    /**
     * Executes the rule associated with the specified auth item.
     *
     * If the item does not specify a rule, this method will return true. Otherwise, it will
     * return the value of [[Rule::execute()]].
     *
     * @param string|int $user   the user ID. This should be either an integer or a string representing
     *                           the unique identifier of a user. See [[\yii\web\User::id]].
     * @param Item       $item   the auth item that needs to execute its rule
     * @param array      $params parameters passed to [[CheckAccessInterface::checkAccess()]] and will be passed to the rule
     *
     * @throws InvalidConfigException if the auth item has an invalid rule.
     *
     * @return bool the return value of [[Rule::execute()]]. If the auth item does not specify a rule, true will be returned.
     */
    protected function executeRule($user, $item, $params)
    {
        if ($item->ruleName === null) {
            return true;
        }
        $rule = $this->getRule($item->ruleName);
        if ($rule instanceof Rule) {
            return $rule->execute($user, $item, $params);
        }

        throw new InvalidConfigException("Rule not found: {$item->ruleName}");
    }

    /**
     * Checks whether array of $assignments is empty and [[defaultRoles]] property is empty as well.
     *
     * @param Assignment[] $assignments array of user's assignments
     *
     * @return bool whether array of $assignments is empty and [[defaultRoles]] property is empty as well
     *
     * @since 2.0.11
     */
    protected function hasNoAssignments(array $assignments)
    {
        return empty($assignments) && empty($this->defaultRoles);
    }
}


<?php
/**
 * @link http://www.yiiframework.com/
 *
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
use Yiisoft\Db\Migration;
use Yiisoft\Rbac\BaseManager;
use Yiisoft\Rbac\DbManager;
use Yiisoft\Rbac\Exceptions\InvalidConfigException;

/**
 * Adds index on `user_id` column in `auth_assignment` table for performance reasons.
 *
 * @see https://github.com/yiisoft/yii2/pull/14765
 *
 * @author Ivan Buttinoni <ivan.buttinoni@cibi.it>
 *
 * @since 2.0.13
 */
class m170907_052038_rbac_add_index_on_auth_assignment_user_id extends Migration
{
    public $column = 'user_id';
    public $index = 'auth_assignment_user_id_idx';

    protected $authManager;

    public function __construct(BaseManager $authManager)
    {
        $this->authManager = $authManager;
        if (!$this->authManager instanceof DbManager) {
            throw new InvalidConfigException('You should configure "authManager" component to use database before executing this migration.');
        }
    }

    /**
     * {@inheritdoc}
     */
    public function up()
    {
        $this->createIndex($this->index, $this->authManager->assignmentTable, $this->column);
    }

    /**
     * {@inheritdoc}
     */
    public function down()
    {
        $this->dropIndex($this->index, $this->authManager->assignmentTable);
    }
}