diff --git a/.gitattributes b/.gitattributes index 412eeda7..61a4ef2b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,12 @@ +/.github export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/Tests export-ignore +/php_cs.dist export-ignore +/phpunit.xml.dist export-ignore +/rector.php export-ignore + # Auto detect text files and perform LF normalization * text=auto diff --git a/.github/workflows/continuous-integration.yaml b/.github/workflows/continuous-integration.yaml new file mode 100644 index 00000000..18edee9f --- /dev/null +++ b/.github/workflows/continuous-integration.yaml @@ -0,0 +1,60 @@ +name: CI + +on: + push: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + build-test: + runs-on: ubuntu-latest + name: 'PHPUnit (PHP ${{ matrix.php-version }}, Symfony ${{ matrix.symfony-version }} + ${{ matrix.dependencies }} deps)' + strategy: + fail-fast: false + matrix: + php-version: + - '8.1' + - '8.4' + symfony-version: + - '^5.4' + - '^6.4' + - '^7.2' + dependencies: + - 'highest' + exclude: + - php-version: '8.1' + symfony-version: '^7.2' + include: + - php-version: '7.4' + symfony-version: '^4.4' + dependencies: 'highest' + - php-version: '7.4' + symfony-version: '^5.0' + dependencies: 'lowest' + steps: + - name: 'Checkout' + uses: actions/checkout@v3 + + - name: 'Setup PHP' + uses: 'shivammathur/setup-php@v2' + with: + php-version: '${{ matrix.php-version }}' + coverage: 'none' + tools: 'composer:v2, flex' + extensions: 'curl, json, intl, mbstring, mongodb, openssl' + + - name: 'Install Composer dependencies' + uses : 'ramsey/composer-install@v2' + with: + dependency-versions: "${{ matrix.dependencies }}" + composer-options: "--no-interaction" + env: + COMPOSER_FUND: '0' + SYMFONY_REQUIRE: '${{ matrix.symfony-version }}' + + - name: 'Run unit tests' + run: | + vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index b308ad93..62690a29 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ catalog.xml .buildpath .project .settings/ - +.idea \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index ed5670b8..18e84fc2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,50 @@ language: php php: - - 5.3 - - 5.4 - - 5.5 - - 7.0 + - 7.2 + - 7.3 + - 7.4 matrix: - allow_failures: - - php: 7.0 + include: + - php: 7.2 + env: | + SYMFONY_VERSION=^3.0 + - php: 7.2 + env: | + SYMFONY_VERSION=^4.0 + - php: 7.4 + env: | + SYMFONY_VERSION=^3.0 + - php: 7.4 + env: | + SYMFONY_VERSION=^4.0 -env: - - SYMFONY_VERSION=origin/master +before_install: + - echo "extension = mongodb.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - | + if [ "${SYMFONY_VERSION}" != "" ]; then + packages="form dependency-injection config http-foundation http-kernel options-resolver security-guard serializer" + devpackages="framework-bundle security-bundle twig-bundle expression-language" + for package in $packages + do + composer require --no-update symfony/"$package"=${SYMFONY_VERSION}; + done + for package in $devpackages + do + composer require --dev --no-update symfony/"$package"=${SYMFONY_VERSION}; + done + fi; -before_script: - - curl -s http://getcomposer.org/installer | php - - php composer.phar install +install: + - travis_retry composer self-update + - COMPOSER_MEMORY_LIMIT=-1 travis_retry composer update ${COMPOSER_FLAGS} --no-interaction + - curl -s http://getcomposer.org/installer | php + - php composer.phar install --dev --no-interaction script: - - phpunit + - mkdir -p build/logs + - php vendor/bin/phpunit -c phpunit.xml.dist -notifications: - email: - - abhoryo@free.fr +after_success: + - php vendor/bin/php-coveralls diff --git a/APYDataGridBundle.php b/APYDataGridBundle.php index a65e55ec..e986e184 100644 --- a/APYDataGridBundle.php +++ b/APYDataGridBundle.php @@ -12,16 +12,18 @@ namespace APY\DataGridBundle; -use Symfony\Component\HttpKernel\Bundle\Bundle; -use Symfony\Component\DependencyInjection\ContainerBuilder; use APY\DataGridBundle\DependencyInjection\Compiler\GridExtensionPass; +use APY\DataGridBundle\DependencyInjection\Compiler\GridPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Bundle\Bundle; class APYDataGridBundle extends Bundle { - public function build(ContainerBuilder $container) + public function build(ContainerBuilder $container): void { parent::build($container); $container->addCompilerPass(new GridExtensionPass()); + $container->addCompilerPass(new GridPass()); } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a4e694b..62be18d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,47 @@ +CHANGELOG +========= + +2.3 (WIP) +--------- + +### New feature + +* Add `GridBuilder`, `GridConfig` and `GridFactory` to make easier the grid constructor. +* Add `GridType`, `GridRegistry` for build the grid in a separate class, which can then be reused. + +### Bugfix + +- Fix #739 - Fix filters with undefined index error + +2.2 or earlier +-------------- + `14 October 2012` + * Don't redirect with an AJAX request `13 October 2012` + * Fix bug - Fixed errors with default values in Column __initialize. * Add escape option for the value of a cell `7 October 2012` + * Fix bug - Fix file Content-Length calculation for export. `2 October 2012` + * Fix bug - Fix field name in DQL for multi level entities `21 September 2012` + * Fix bug - Wrong regex for the eq operator for the vector source and the setData function * Fix bug - Fix excpetion in ArrayColumn class * Add pagerfanta to the configuration * Add a columns order function `7 September 2012` + * Fix #241 - Numeric and boolean filter don't work with Vector source and setData function * Fix #224 - Fix wrong default junction with multi select feature * Fix #239 - Fix complexes alias names conflict @@ -27,29 +51,36 @@ * Add DQL function support with non mapped fields `4 September 2012` + * Fix #237 - Add distinct support for DQL aggregate function * Add abbrevation support with __abbr in translation file * Fix #236 See https://bugs.php.net/bug.php?id=62464 `27 August 2012` + * Fix #229 - Fix pagerfanta with PHP < 5.4 `18 August 2012` + * Fix #227 - Bug with annotations config groups `17 August 2012` + * Add role control on massAction, rowAction and Export * Fix bug : grid not found when you export a grid with an searchOnClick column * Fix persistence with basic authentification `14 August 2012` + * Add grid data conjunction setting * Fix bug : wrong convert charset for exports `9 August 2012` + * Fix datetime select filter `8 August 2012` + * Add the default size and separator default config `31 July 2012` @@ -350,4 +381,4 @@ * possible registrations of custom column types in container * look at Resources/Config/services * grid need to be created as service * annotations - * ODM support \ No newline at end of file + * ODM support diff --git a/DependencyInjection/APYDataGridExtension.php b/DependencyInjection/APYDataGridExtension.php index fec8fe8d..8eba8f8f 100644 --- a/DependencyInjection/APYDataGridExtension.php +++ b/DependencyInjection/APYDataGridExtension.php @@ -12,22 +12,26 @@ namespace APY\DataGridBundle\DependencyInjection; -use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; -use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; class APYDataGridExtension extends Extension { - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader = new XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('services.xml'); $loader->load('columns.xml'); + $ymlLoader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); + $ymlLoader->load('grid.yml'); + $container->setParameter('apy_data_grid.limits', $config['limits']); $container->setParameter('apy_data_grid.theme', $config['theme']); $container->setParameter('apy_data_grid.persistence', $config['persistence']); diff --git a/DependencyInjection/Compiler/GridExtensionPass.php b/DependencyInjection/Compiler/GridExtensionPass.php index 52906435..baf75bf0 100644 --- a/DependencyInjection/Compiler/GridExtensionPass.php +++ b/DependencyInjection/Compiler/GridExtensionPass.php @@ -12,13 +12,13 @@ namespace APY\DataGridBundle\DependencyInjection\Compiler; -use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; class GridExtensionPass implements CompilerPassInterface { - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (false === $container->hasDefinition('grid')) { return; @@ -31,10 +31,10 @@ public function process(ContainerBuilder $container) // afterward. If not, the globals from the extensions will never // be registered. $calls = $definition->getMethodCalls(); - $definition->setMethodCalls(array()); + $definition->setMethodCalls([]); foreach ($container->findTaggedServiceIds('grid.column.extension') as $id => $attributes) { - $definition->addMethodCall('addColumnExtension', array(new Reference($id))); + $definition->addMethodCall('addColumnExtension', [new Reference($id)]); } $definition->setMethodCalls(array_merge($definition->getMethodCalls(), $calls)); diff --git a/DependencyInjection/Compiler/GridPass.php b/DependencyInjection/Compiler/GridPass.php new file mode 100644 index 00000000..d3661469 --- /dev/null +++ b/DependencyInjection/Compiler/GridPass.php @@ -0,0 +1,41 @@ +hasDefinition('apy_grid.registry')) { + return; + } + + $definition = $container->getDefinition('apy_grid.registry'); + + $types = $container->findTaggedServiceIds('apy_grid.type'); + foreach ($types as $id => $tag) { + $definition->addMethodCall('addType', [new Reference($id)]); + } + + $columns = $container->findTaggedServiceIds('apy_grid.column'); + foreach ($columns as $id => $tag) { + $definition->addMethodCall('addColumn', [new Reference($id)]); + } + } +} diff --git a/DependencyInjection/Compiler/TranslationPass.php b/DependencyInjection/Compiler/TranslationPass.php new file mode 100644 index 00000000..514f51eb --- /dev/null +++ b/DependencyInjection/Compiler/TranslationPass.php @@ -0,0 +1,32 @@ +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); + } +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index e8ed2c7e..e212566e 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -6,33 +6,33 @@ use Symfony\Component\Config\Definition\ConfigurationInterface; /** - * This is the class that validates and merges configuration from your app/config files + * This is the class that validates and merges configuration from your app/config files. * * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class} */ class Configuration implements ConfigurationInterface { /** - * {@inheritDoc} + * {@inheritdoc} */ - public function getConfigTreeBuilder() + public function getConfigTreeBuilder(): TreeBuilder { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('apy_data_grid'); + $treeBuilder = new TreeBuilder('apy_data_grid'); + $rootNode = $treeBuilder->getRootNode(); $rootNode ->children() ->arrayNode('limits') ->performNoDeepMerging() ->beforeNormalization() - ->ifTrue(function($v) { return !is_array($v); }) - ->then(function($v) { return array($v); }) + ->ifTrue(fn($v) => !is_array($v)) + ->then(fn($v) => [$v]) ->end() - ->defaultValue(array(20 => '20', 50 => '50', 100 => '100')) + ->defaultValue([20 => '20', 50 => '50', 100 => '100']) ->prototype('scalar')->end() ->end() ->booleanNode('persistence')->defaultFalse()->end() - ->scalarNode('theme')->defaultValue('APYDataGridBundle::blocks.html.twig')->end() + ->scalarNode('theme')->defaultValue('@APYDataGrid/blocks.html.twig')->end() ->scalarNode('no_data_message')->defaultValue('No data')->end() ->scalarNode('no_result_message')->defaultValue('No result')->end() ->scalarNode('actions_columns_size')->defaultValue(-1)->end() @@ -44,15 +44,14 @@ public function getConfigTreeBuilder() ->booleanNode('enable')->defaultFalse()->end() ->scalarNode('view_class')->defaultValue('Pagerfanta\View\DefaultView')->end() ->arrayNode('options') - ->defaultValue(array('prev_message' => '«', 'next_message' => '»')) + ->defaultValue(['prev_message' => '«', 'next_message' => '»']) ->useAttributeAsKey('options') ->prototype('scalar')->end() ->end() ->end() ->end() - ->end() + ->end(); - ; return $treeBuilder; } } diff --git a/Grid/AbstractType.php b/Grid/AbstractType.php new file mode 100644 index 00000000..2783e6b2 --- /dev/null +++ b/Grid/AbstractType.php @@ -0,0 +1,32 @@ +title = $title; $this->callback = $callback; $this->confirm = $confirm; - $this->confirmMessage = 'Do you want to '.strtolower($title).' the selected rows?'; + $this->confirmMessage = 'Do you want to ' . strtolower($title) . ' the selected rows?'; $this->parameters = $parameters; $this->role = $role; } + // @todo: has this setter sense? we passed the title from constructor /** - * Set action title + * Set action title. * - * @param $title + * @param string $title * * @return self */ @@ -54,7 +67,7 @@ public function setTitle($title) } /** - * get action title + * get action title. * * @return string */ @@ -64,9 +77,9 @@ public function getTitle() } /** - * Set action callback + * Set action callback. * - * @param $callback + * @param string $callback * * @return self */ @@ -78,7 +91,7 @@ public function setCallback($callback) } /** - * get action callback + * get action callback. * * @return string */ @@ -87,10 +100,12 @@ public function getCallback() return $this->callback; } + // @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 + * Set action confirm. * - * @param $confirm + * @param bool $confirm * * @return self */ @@ -101,10 +116,11 @@ public function setConfirm($confirm) return $this; } + // @todo: could we change this to neddConfirm? /** - * Get action confirm + * Get action confirm. * - * @return boolean + * @return bool */ public function getConfirm() { @@ -112,7 +128,7 @@ public function getConfirm() } /** - * Set action confirmMessage + * Set action confirmMessage. * * @param string $confirmMessage * @@ -126,7 +142,7 @@ public function setConfirmMessage($confirmMessage) } /** - * get action confirmMessage + * get action confirmMessage. * * @return string */ @@ -136,9 +152,10 @@ public function getConfirmMessage() } /** - * Set action/controller parameters + * Set action/controller parameters. * * @param array $parameters + * * @return $this */ public function setParameters(array $parameters) @@ -149,7 +166,7 @@ public function setParameters(array $parameters) } /** - * Get action/controller parameters + * Get action/controller parameters. * * @return array */ @@ -159,9 +176,9 @@ public function getParameters() } /** - * set role + * set role. * - * @param mixed $role + * @param string $role * * @return self */ @@ -173,9 +190,9 @@ public function setRole($role) } /** - * Get role + * Get role. * - * @return mixed + * @return string */ public function getRole() { diff --git a/Grid/Action/MassActionInterface.php b/Grid/Action/MassActionInterface.php index 9f0598f5..72ba96df 100644 --- a/Grid/Action/MassActionInterface.php +++ b/Grid/Action/MassActionInterface.php @@ -15,35 +15,35 @@ interface MassActionInterface { /** - * get action title + * get action title. * * @return string */ public function getTitle(); /** - * get action callback + * get action callback. * * @return string */ public function getCallback(); /** - * get action confirm + * get action confirm. * - * @return boolean + * @return bool */ public function getConfirm(); /** - * get action confirmMessage + * get action confirmMessage. * - * @return boolean + * @return bool */ public function getConfirmMessage(); /** - * get additional parameters + * get additional parameters. * * @return array */ diff --git a/Grid/Action/RowAction.php b/Grid/Action/RowAction.php index 6f40082d..5e11bd0b 100644 --- a/Grid/Action/RowAction.php +++ b/Grid/Action/RowAction.php @@ -12,45 +12,72 @@ 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'; - protected $routeParameters = array(); - protected $routeParametersMapping = array(); - protected $attributes = array(); + + /** @var array */ + protected $routeParameters = []; + + /** @var array */ + protected $routeParametersMapping = []; + + /** @var array */ + protected $attributes = []; + + /** @var string|null */ protected $role; - protected $callback; + + /** @var array */ + protected $callbacks = []; + + /** @var bool */ + protected $enabled = true; /** - * Default RowAction constructor + * Default RowAction constructor. * - * @param string $title Title of the row action - * @param string $route Route to the row action - * @param boolean $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 + * @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 = array(), $role = null) + 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->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 + * Set action title. * * @param string $title * @@ -64,19 +91,18 @@ public function setTitle($title) } /** - * get action title - * - * @return string + * {@inheritdoc} */ public function getTitle() { return $this->title; } + // @todo: has this setter real sense? we passed this value from constructor /** - * Set action route + * Set action route. * - * @param string $route + * @param string $route * * @return self */ @@ -88,19 +114,19 @@ public function setRoute($route) } /** - * get action route - * - * @return string + * {@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 + * Set action confirm. * - * @param $confirm + * @param bool $confirm * * @return self */ @@ -112,9 +138,7 @@ public function setConfirm($confirm) } /** - * get action confirm - * - * @return boolean + * {@inheritdoc} */ public function getConfirm() { @@ -122,7 +146,7 @@ public function getConfirm() } /** - * Set action confirmMessage + * Set action confirmMessage. * * @param string $confirmMessage * @@ -136,9 +160,7 @@ public function setConfirmMessage($confirmMessage) } /** - * get action confirmMessage - * - * @return string + * {@inheritdoc} */ public function getConfirmMessage() { @@ -146,7 +168,7 @@ public function getConfirmMessage() } /** - * Set action target + * Set action target. * * @param string $target * @@ -160,9 +182,7 @@ public function setTarget($target) } /** - * get action target - * - * @return string + * {@inheritdoc} */ public function getTarget() { @@ -170,7 +190,7 @@ public function getTarget() } /** - * Set action column + * Set action column. * * @param string $column Identifier of the action column * @@ -184,9 +204,7 @@ public function setColumn($column) } /** - * get action column - * - * @return \APY\DataGridBundle\Grid\Column\Column + * {@inheritdoc} */ public function getColumn() { @@ -194,7 +212,7 @@ public function getColumn() } /** - * Add route parameter + * Add route parameter. * * @param array|string $routeParameters * @@ -205,7 +223,7 @@ public function addRouteParameters($routeParameters) $routeParameters = (array) $routeParameters; foreach ($routeParameters as $key => $routeParameter) { - if(is_int($key)) { + if (is_int($key)) { $this->routeParameters[] = $routeParameter; } else { $this->routeParameters[$key] = $routeParameter; @@ -216,7 +234,7 @@ public function addRouteParameters($routeParameters) } /** - * Set route parameters + * Set route parameters. * * @param array|string $routeParameters * @@ -230,17 +248,16 @@ public function setRouteParameters($routeParameters) } /** - * get route parameters - * - * @return array + * {@inheritdoc} */ public function getRouteParameters() { return $this->routeParameters; } + // @todo: why is this accepting string? it seems pretty useless, isn't it? /** - * Set route parameters mapping + * Set route parameters mapping. * * @param array|string $routeParametersMapping * @@ -254,18 +271,19 @@ public function setRouteParametersMapping($routeParametersMapping) } /** - * Map the parameter + * Map the parameter. * * @param string $name parameter + * * @return null|string */ public function getRouteParametersMapping($name) { - return (isset($this->routeParametersMapping[$name]) ? $this->routeParametersMapping[$name] : null); + return isset($this->routeParametersMapping[$name]) ? $this->routeParametersMapping[$name] : null; } /** - * Set attributes + * Set attributes. * * @param array $attributes * @@ -279,7 +297,7 @@ public function setAttributes(array $attributes) } /** - * Add attribute + * Add attribute. * * @param string $name * @param string $value @@ -294,9 +312,7 @@ public function addAttribute($name, $value) } /** - * Get attributes - * - * @return array + * {@inheritdoc} */ public function getAttributes() { @@ -304,9 +320,9 @@ public function getAttributes() } /** - * set role + * set role. * - * @param mixed $role + * @param string $role * * @return self */ @@ -318,9 +334,9 @@ public function setRole($role) } /** - * Get role + * Get role. * - * @return mixed + * @return string */ public function getRole() { @@ -328,30 +344,82 @@ public function getRole() } /** - * Set render callback + * Set render callback. + * + * @deprecated This is deprecated and will be removed in 3.0; use addManipulateRender instead. + * + * @param \Closure $callback * - * @param $callback * @return self */ - public function manipulateRender($callback) + public function manipulateRender(\Closure $callback) { - $this->callback = $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 + * Render action for row. * - * @param \APY\DataGridBundle\Grid\Row $row - * @return null|RowAction + * @param Row $row + * + * @return RowAction|null */ public function render($row) { - if (is_callable($this->callback)) { - return call_user_func($this->callback, $this, $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; + } + + /** + * Get the value of callbacks + */ + public function getCallbacks() + { + return $this->callbacks; + } } diff --git a/Grid/Action/RowActionInterface.php b/Grid/Action/RowActionInterface.php index 15c65c63..f21f9595 100644 --- a/Grid/Action/RowActionInterface.php +++ b/Grid/Action/RowActionInterface.php @@ -12,61 +12,71 @@ namespace APY\DataGridBundle\Grid\Action; +// @todo: implementation seems to be more specific than interface. It obviously be the case but I've noticed that +// only one method of this interface is used in our code. So I wonder if this interface is "updated" and is the mimimum +// API methods that should be provided as a contract or not. interface RowActionInterface { /** - * get action title + * get action title. * * @return string */ public function getTitle(); /** - * get action route + * get action route. * * @return string */ public function getRoute(); /** - * get action confirm + * get action confirm. * - * @return boolean + * @return bool */ public function getConfirm(); /** - * get action confirmMessage + * get action confirmMessage. * - * @return boolean + * @return string */ public function getConfirmMessage(); /** - * get action target + * get action target. * - * @return boolean + * @return string */ public function getTarget(); /** - * get the action column id + * get the action column id. * - * @return boolean + * @return string */ public function getColumn(); /** - * get route parameters + * get route parameters. * * @return array */ public function getRouteParameters(); /** - * get attributes of the link + * get attributes of the link. * * @return array */ public function getAttributes(); + + /** + * get action enabled. + * + * @return bool + */ + public function getEnabled(); } diff --git a/Grid/Column/ActionsColumn.php b/Grid/Column/ActionsColumn.php index 99ca5116..19d6a0d3 100644 --- a/Grid/Column/ActionsColumn.php +++ b/Grid/Column/ActionsColumn.php @@ -16,29 +16,36 @@ class ActionsColumn extends Column { protected $rowActions; - public function __construct($column, $title, array $rowActions = array()) + /** + * 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(array( + parent::__construct([ 'id' => $column, 'title' => $title, 'sortable' => false, 'source' => false, - 'filterable' => true // Show a reset link instead of a filter - )); + 'filterable' => true, // Show a reset link instead of a filter + ]); } public function getRouteParameters($row, $action) { $actionParameters = $action->getRouteParameters(); - if(!empty($actionParameters)) { - $routeParameters = array(); + if (!empty($actionParameters)) { + $routeParameters = []; foreach ($actionParameters as $name => $parameter) { - if(is_int($name)) { - if(($name = $action->getRouteParametersMapping($parameter)) === null) { + if (is_int($name)) { + if (($name = $action->getRouteParametersMapping($parameter)) === null) { $name = $this->getValidRouteParameters($parameter); } $routeParameters[$name] = $row->getField($parameter); @@ -50,14 +57,14 @@ public function getRouteParameters($row, $action) return $routeParameters; } - return array($row->getPrimaryField() => $row->getPrimaryFieldValue()); + 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); + $name = substr($name, 0, $pos) . strtoupper(substr($name, $pos + 1, 1)) . substr($name, $pos + 2); } return $name; @@ -90,18 +97,19 @@ public function getFilterType() } /** - * Get the list of actions to render + * Get the list of actions to render. * * @param $row + * * @return array */ public function getActionsToRender($row) { $list = $this->rowActions; - foreach($list as $i=>$a) { + foreach ($list as $i => $a) { $action = clone $a; $list[$i] = $action->render($row); - if(null === $list[$i]) { + if (null === $list[$i]) { unset($list[$i]); } } diff --git a/Grid/Column/ArrayColumn.php b/Grid/Column/ArrayColumn.php index 65f741ca..145c15db 100644 --- a/Grid/Column/ArrayColumn.php +++ b/Grid/Column/ArrayColumn.php @@ -20,53 +20,56 @@ public function __initialize(array $params) { parent::__initialize($params); - $this->setOperators($this->getParam('operators', array( + $this->setOperators($this->getParam('operators', [ self::OPERATOR_LIKE, self::OPERATOR_NLIKE, self::OPERATOR_EQ, self::OPERATOR_NEQ, self::OPERATOR_ISNULL, self::OPERATOR_ISNOTNULL, - ))); - $this->setDefaultOperator($this->getParam('defaultOperator', self::OPERATOR_LIKE)); + ])); } public function getFilters($source) { $parentFilters = parent::getFilters($source); - $filters = array(); - foreach($parentFilters as $filter) { - switch ($filter->getOperator()) { - case self::OPERATOR_EQ: - case self::OPERATOR_NEQ: - $filterValues = (array) $filter->getValue(); - $value = ''; - $counter = 1; - foreach ($filterValues as $filterValue) { - $len = strlen($filterValue); - $value .= 'i:'.$counter++.';s:'.$len.':"'.$filterValue.'";'; - } + $filters = []; + foreach ($parentFilters as $filter) { + if ($source === 'document') { + $filters[] = $filter; + } else { + switch ($filter->getOperator()) { + case self::OPERATOR_EQ: + case self::OPERATOR_NEQ: + $filterValues = (array) $filter->getValue(); + $value = ''; + $counter = 1; + foreach ($filterValues as $filterValue) { + $len = strlen($filterValue); + $value .= 'i:' . $counter++ . ';s:' . $len . ':"' . $filterValue . '";'; + } - $filters[] = new Filter($filter->getOperator(), 'a:'.count($filterValues).':{'.$value.'}'); - break; - case self::OPERATOR_LIKE: - case self::OPERATOR_NLIKE: - $len = strlen($filter->getValue()); - $value = 's:'.$len.':"'.$filter->getValue().'";'; - $filters[] = new Filter($filter->getOperator(), $value); - break; - case self::OPERATOR_ISNULL: - $filters[] = new Filter(self::OPERATOR_ISNULL); - $filters[] = new Filter(self::OPERATOR_EQ, 'a:0:{}'); - $this->setDataJunction(self::DATA_DISJUNCTION); - break; - case self::OPERATOR_ISNOTNULL: - $filters[] = new Filter(self::OPERATOR_ISNOTNULL); - $filters[] = new Filter(self::OPERATOR_NEQ, 'a:0:{}'); - break; - default: - $filters[] = $filter; + $filters[] = new Filter($filter->getOperator(), 'a:' . count($filterValues) . ':{' . $value . '}'); + break; + case self::OPERATOR_LIKE: + case self::OPERATOR_NLIKE: + $len = strlen($filter->getValue()); + $value = 's:' . $len . ':"' . $filter->getValue() . '";'; + $filters[] = new Filter($filter->getOperator(), $value); + break; + case self::OPERATOR_ISNULL: + $filters[] = new Filter(self::OPERATOR_ISNULL); + $filters[] = new Filter(self::OPERATOR_EQ, 'a:0:{}'); + $this->setDataJunction(self::DATA_DISJUNCTION); + break; + case self::OPERATOR_ISNOTNULL: + $filters[] = new Filter(self::OPERATOR_ISNOTNULL); + $filters[] = new Filter(self::OPERATOR_NEQ, 'a:0:{}'); + break; + default: + $filters[] = $filter; + } } } @@ -79,10 +82,11 @@ public function renderCell($values, $row, $router) return call_user_func($this->callback, $values, $row, $router); } - $return = array(); - if(is_array($values) || $values instanceof \Traversable) { + $return = []; + if (is_array($values) || $values instanceof \Traversable) { foreach ($values as $key => $value) { - if (!is_array($value) && isset($this->values[(string)$value])) { + // @todo: this seems like dead code + if (!is_array($value) && isset($this->values[(string) $value])) { $value = $this->values[$value]; } diff --git a/Grid/Column/BooleanColumn.php b/Grid/Column/BooleanColumn.php old mode 100755 new mode 100644 index 372346a4..f3aebccc --- a/Grid/Column/BooleanColumn.php +++ b/Grid/Column/BooleanColumn.php @@ -1,6 +1,6 @@ @@ -18,7 +18,7 @@ public function __initialize(array $params) { $params['filter'] = 'select'; $params['selectFrom'] = 'values'; - $params['operators'] = array(self::OPERATOR_EQ); + $params['operators'] = [self::OPERATOR_EQ]; $params['defaultOperator'] = self::OPERATOR_EQ; $params['operatorsVisible'] = false; $params['selectMulti'] = false; @@ -27,13 +27,13 @@ public function __initialize(array $params) $this->setAlign($this->getParam('align', 'center')); $this->setSize($this->getParam('size', '30')); - $this->setValues($this->getParam('values', array(1 => 'true', 0 => 'false'))); + $this->setValues($this->getParam('values', [1 => 'true', 0 => 'false'])); } public function isQueryValid($query) { $query = (array) $query; - if ($query[0] === true || $query[0] === false || $query[0] == 0 || $query[0] == 1 ) { + if ($query[0] === true || $query[0] === false || $query[0] == 0 || $query[0] == 1) { return true; } diff --git a/Grid/Column/Column.php b/Grid/Column/Column.php old mode 100755 new mode 100644 index 808c4e65..1acc0b64 --- a/Grid/Column/Column.php +++ b/Grid/Column/Column.php @@ -12,54 +12,77 @@ namespace APY\DataGridBundle\Grid\Column; -use Symfony\Component\Security\Core\SecurityContextInterface; 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'; - - /** - * Align - */ - const ALIGN_LEFT = 'left'; - const ALIGN_RIGHT = 'right'; - const ALIGN_CENTER = 'center'; - - protected static $aligns = array( + public const DEFAULT_VALUE = null; + + /** + * Filter. + */ + public const DATA_CONJUNCTION = 0; + public const DATA_DISJUNCTION = 1; + + public const OPERATOR_EQ = 'eq'; + public const OPERATOR_NEQ = 'neq'; + public const OPERATOR_LT = 'lt'; + public const OPERATOR_LTE = 'lte'; + public const OPERATOR_GT = 'gt'; + public const OPERATOR_GTE = 'gte'; + public const OPERATOR_BTW = 'btw'; + public const OPERATOR_BTWE = 'btwe'; + public const OPERATOR_LIKE = 'like'; + public const OPERATOR_NLIKE = 'nlike'; + public const OPERATOR_RLIKE = 'rlike'; + public const OPERATOR_LLIKE = 'llike'; + public const OPERATOR_SLIKE = 'slike'; //simple/strict LIKE + public const OPERATOR_NSLIKE = 'nslike'; + public const OPERATOR_RSLIKE = 'rslike'; + public const OPERATOR_LSLIKE = 'lslike'; + + public const OPERATOR_ISNULL = 'isNull'; + public 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. + */ + public const ALIGN_LEFT = 'left'; + public const ALIGN_RIGHT = 'right'; + public const ALIGN_CENTER = 'center'; + + protected static $aligns = [ self::ALIGN_LEFT, self::ALIGN_RIGHT, self::ALIGN_CENTER, - ); + ]; /** - * Internal parameters + * Internal parameters. */ protected $id; protected $title; @@ -79,12 +102,12 @@ abstract class Column protected $params; protected $isSorted = false; protected $orderUrl; - protected $securityContext; + protected $authorizationChecker; protected $data; protected $operatorsVisible; protected $operators; protected $defaultOperator; - protected $values = array(); + protected $values = []; protected $selectFrom; protected $selectMulti; protected $selectExpanded; @@ -97,12 +120,12 @@ abstract class Column protected $isManualField; protected $isAggregate; protected $usePrefixTitle; + protected $translationDomain; protected $dataJunction = self::DATA_CONJUNCTION; - /** - * Default Column constructor + * Default Column constructor. * * @param array $params */ @@ -115,7 +138,7 @@ public function __initialize(array $params) { $this->params = $params; $this->setId($this->getParam('id')); - $this->setTitle($this->getParam('title', '')); + $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)); @@ -130,14 +153,14 @@ public function __initialize(array $params) $this->setJoinType($this->getParam('joinType')); $this->setFilterType($this->getParam('filter', 'input')); $this->setSelectFrom($this->getParam('selectFrom', 'query')); - $this->setValues($this->getParam('values', array())); + $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', array( + $this->setOperators($this->getParam('operators', [ self::OPERATOR_EQ, self::OPERATOR_NEQ, self::OPERATOR_LT, @@ -156,15 +179,16 @@ public function __initialize(array $params) 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', "
")); + $this->setSeparator($this->getParam('separator', '
')); $this->setExport($this->getParam('export')); $this->setClass($this->getParam('class')); + $this->setTranslationDomain($this->getParam('translation_domain')); } protected function getParam($id, $default = null) @@ -173,11 +197,12 @@ protected function getParam($id, $default = null) } /** - * Draw cell + * Draw cell. * * @param string $value - * @param Row $row + * @param Row $row * @param $router + * * @return string */ public function renderCell($value, $row, $router) @@ -186,8 +211,8 @@ public function renderCell($value, $row, $router) 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 = is_bool($value) ? (int) $value : $value; + if (array_key_exists((string) $value, $this->values)) { $value = $this->values[$value]; } @@ -195,9 +220,10 @@ public function renderCell($value, $row, $router) } /** - * Set column callback + * Set column callback. * * @param $callback + * * @return self */ public function manipulateRenderCell($callback) @@ -208,9 +234,10 @@ public function manipulateRenderCell($callback) } /** - * Set column identifier + * Set column identifier. * * @param $id + * * @return self */ public function setId($id) @@ -221,7 +248,7 @@ public function setId($id) } /** - * get column identifier + * get column identifier. * * @return int|string */ @@ -231,20 +258,21 @@ public function getId() } /** - * get column render block identifier + * get column render block identifier. * * @return int|string */ public function getRenderBlockId() { // For Mapping fields and aggregate dql functions - return str_replace(array('.', ':'), '_', $this->id); + return str_replace(['.', ':'], '_', $this->id); } /** - * Set column title + * Set column title. * * @param string $title + * * @return \APY\DataGridBundle\Grid\Column\Column */ public function setTitle($title) @@ -255,7 +283,7 @@ public function setTitle($title) } /** - * Get column title + * Get column title. * * @return string */ @@ -264,11 +292,11 @@ public function getTitle() return $this->title; } - /** - * Set column visibility + * Set column visibility. + * + * @param bool $visible * - * @param boolean $visible * @return $this */ public function setVisible($visible) @@ -279,7 +307,7 @@ public function setVisible($visible) } /** - * Return column visibility + * Return column visibility. * * @return bool return true when column is visible */ @@ -287,15 +315,15 @@ public function isVisible($isExported = false) { $visible = $isExported && $this->export !== null ? $this->export : $this->visible; - if ($visible && $this->securityContext !== null && $this->getRole() != null) { - return $this->securityContext->isGranted($this->getRole()); + if ($visible && $this->authorizationChecker !== null && $this->getRole() !== null) { + return $this->authorizationChecker->isGranted($this->getRole()); } return $visible; } /** - * Return true if column is sorted + * Return true if column is sorted. * * @return bool return true when column is sorted */ @@ -312,7 +340,7 @@ public function setSortable($sortable) } /** - * column ability to sort + * column ability to sort. * * @return bool return true when column can be sorted */ @@ -322,17 +350,72 @@ public function isSortable() } /** - * Return true if column is filtered + * Return true if column is filtered. * - * @return boolean return true when column is filtered + * @return bool return true when column is filtered */ public function isFiltered() { - return ( (isset($this->data['from']) && $this->isQueryValid($this->data['from']) && $this->data['from'] != static::DEFAULT_VALUE) - || (isset($this->data['to']) && $this->isQueryValid($this->data['to']) && $this->data['to'] != static::DEFAULT_VALUE) - || (isset($this->data['operator']) && ($this->data['operator'] === self::OPERATOR_ISNULL || $this->data['operator'] === self::OPERATOR_ISNOTNULL)) ); + if ($this->hasFromOperandFilter()) { + return true; + } + + if ($this->hasToOperandFilter()) { + return true; + } + + return $this->hasOperatorFilter(); } + /** + * @return bool + */ + private function hasFromOperandFilter(): bool + { + 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(): bool + { + 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(): bool + { + 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; @@ -341,7 +424,7 @@ public function setFilterable($filterable) } /** - * column ability to filter + * column ability to filter. * * @return bool return true when column can be filtred */ @@ -351,9 +434,10 @@ public function isFilterable() } /** - * set column order + * set column order. * * @param string $order asc|desc + * * @return \APY\DataGridBundle\Grid\Column\Column */ public function setOrder($order) @@ -367,7 +451,7 @@ public function setOrder($order) } /** - * get column order + * get column order. * * @return string asc|desc */ @@ -377,9 +461,10 @@ public function getOrder() } /** - * Set column width + * Set column width. * * @param int $size in pixels + * * @return \APY\DataGridBundle\Grid\Column\Column */ public function setSize($size) @@ -394,7 +479,7 @@ public function setSize($size) } /** - * get column width + * get column width. * * @return int column width in pixels */ @@ -404,14 +489,15 @@ public function getSize() } /** - * set filter data from session | request + * set filter data from session | request. * * @param $data + * * @return \APY\DataGridBundle\Grid\Column\Column */ public function setData($data) { - $this->data = array('operator' => $this->getDefaultOperator(), 'from' => static::DEFAULT_VALUE, 'to' => static::DEFAULT_VALUE); + $this->data = ['operator' => $this->getDefaultOperator(), 'from' => static::DEFAULT_VALUE, 'to' => static::DEFAULT_VALUE]; $hasValue = false; if (isset($data['from']) && $this->isQueryValid($data['from'])) { @@ -424,7 +510,7 @@ public function setData($data) $hasValue = true; } - $isNullOperator = (isset($data['operator']) && ($data['operator'] === self::OPERATOR_ISNULL || $data['operator'] === self::OPERATOR_ISNOTNULL) ); + $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']; } @@ -433,26 +519,30 @@ public function setData($data) } /** - * get filter data from session | request + * get filter data from session | request. * * @return array data */ public function getData() { - $result = array(); - + $result = []; + // PHP 7.4 Notices + if (is_null($this->data)) { + return $result; + } + $hasValue = false; - if ($this->data['from'] != $this::DEFAULT_VALUE) { + if (isset($this->data['from']) && $this->data['from'] != $this::DEFAULT_VALUE) { $result['from'] = $this->data['from']; $hasValue = true; } - if ($this->data['to'] != $this::DEFAULT_VALUE) { + if (isset($this->data['to']) && $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) ); + $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']; } @@ -460,10 +550,15 @@ public function getData() return $result; } + public function getRawData() + { + return $this->data; + } + /** - * 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 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 boolean + * @return bool */ public function isQueryValid($query) { @@ -471,8 +566,10 @@ public function isQueryValid($query) } /** - * Set column visibility for source class + * Set column visibility for source class. + * * @param $visibleForSource + * * @return \APY\DataGridBundle\Grid\Column\Column */ public function setVisibleForSource($visibleForSource) @@ -483,8 +580,9 @@ public function setVisibleForSource($visibleForSource) } /** - * Return true is column in visible for source class - * @return boolean + * Return true is column in visible for source class. + * + * @return bool */ public function isVisibleForSource() { @@ -492,9 +590,10 @@ public function isVisibleForSource() } /** - * Set column as primary + * Set column as primary. + * + * @param bool $primary * - * @param boolean $primary * @return $this */ public function setPrimary($primary) @@ -505,8 +604,9 @@ public function setPrimary($primary) } /** - * Return true is column in primary - * @return boolean + * Return true is column in primary. + * + * @return bool */ public function isPrimary() { @@ -514,8 +614,10 @@ public function isPrimary() } /** - * Set column align + * Set column align. + * * @param string $align left/right/center + * * @return $this */ public function setAlign($align) @@ -530,7 +632,8 @@ public function setAlign($align) } /** - * get column align + * get column align. + * * @return bool */ public function getAlign() @@ -575,9 +678,8 @@ public function getRole() } /** - * Filter + * Filter. */ - public function setFilterType($filterType) { $this->filterType = strtolower($filterType); @@ -592,10 +694,10 @@ public function getFilterType() public function getFilters($source) { - $filters = array(); + $filters = []; - if ($this->hasOperator($this->data['operator'])) { - if ($this instanceof ArrayColumn && in_array($this->data['operator'], array(self::OPERATOR_EQ, self::OPERATOR_NEQ))) { + if (isset($this->data['operator']) && $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']) { @@ -604,7 +706,7 @@ public function getFilters($source) $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']); + $filters[] = new Filter(self::OPERATOR_LT, $this->data['to']); } break; case self::OPERATOR_BTWE: @@ -653,7 +755,7 @@ public function setDataJunction($dataJunction) } /** - * get data filter junction (how column filters are connected with column data) + * get data filter junction (how column filters are connected with column data). * * @return bool self::DATA_CONJUNCTION | self::DATA_DISJUNCTION */ @@ -670,22 +772,25 @@ public function setOperators(array $operators) } /** - * Return column filter operators + * Return column filter operators. * * @return array $operators */ public function getOperators() { - // Issue with Doctrine (See http://www.doctrine-project.org/jira/browse/DDC-1857 and http://www.doctrine-project.org/jira/browse/DDC-1858) - if ($this->hasDQLFunction()) { - return array_intersect($this->operators, array(self::OPERATOR_EQ, + // 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)); + self::OPERATOR_BTWE, ]); } return $this->operators; @@ -693,6 +798,7 @@ public function getOperators() public function setDefaultOperator($defaultOperator) { + // @todo: should this be \InvalidArgumentException? if (!$this->hasOperator($defaultOperator)) { throw new \Exception($defaultOperator . ' operator not found in operators list.'); } @@ -708,10 +814,11 @@ public function getDefaultOperator() } /** - * Return true if $operator is in $operators + * Return true if $operator is in $operators. * * @param string $operator - * @return boolean + * + * @return bool */ public function hasOperator($operator) { @@ -782,14 +889,15 @@ public function hasDQLFunction(&$matches = null) } /** - * Internal function + * Internal function. + * + * @param $authorizationChecker * - * @param $securityContext * @return $this */ - public function setSecurityContext(SecurityContextInterface $securityContext) + public function setAuthorizationChecker(AuthorizationCheckerInterface $authorizationChecker) { - $this->securityContext = $securityContext; + $this->authorizationChecker = $authorizationChecker; return $this; } @@ -829,8 +937,10 @@ public function getSearchOnClick() /** * Allows to set twig escaping parameter (html, js, css, url, html_attr) - * or to display raw value if type is false + * 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) @@ -893,7 +1003,6 @@ public function getClass() return $this->class; } - public function setIsManualField($isManualField) { $this->isManualField = $isManualField; @@ -922,9 +1031,63 @@ public function getUsePrefixTitle() 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; + } + + /** + * Get the value of params + */ + public function getParams() + { + return $this->params; + } + + /** + * Get the value of callback + */ + public function getCallback() + { + return $this->callback; + } + + /** + * Get the value of authorizationChecker + */ + public function getAuthorizationChecker() + { + return $this->authorizationChecker; + } } diff --git a/Grid/Column/DateColumn.php b/Grid/Column/DateColumn.php index 72eb4944..8689e7b7 100644 --- a/Grid/Column/DateColumn.php +++ b/Grid/Column/DateColumn.php @@ -20,12 +20,14 @@ class DateColumn extends DateTimeColumn protected $fallbackFormat = 'Y-m-d'; + protected $fallbackInputFormat = 'Y-m-d'; + public function getFilters($source) { $parentFilters = parent::getFilters($source); - $filters = array(); - foreach($parentFilters as $filter) { + $filters = []; + foreach ($parentFilters as $filter) { if ($filter->getValue() !== null) { $dateFrom = $filter->getValue(); $dateFrom->setTime(0, 0, 0); @@ -35,26 +37,26 @@ public function getFilters($source) switch ($filter->getOperator()) { case self::OPERATOR_EQ: - $filters[] = new Filter(self::OPERATOR_GTE, $dateFrom); - $filters[] = new Filter(self::OPERATOR_LTE, $dateTo); + $filters[] = new Filter(self::OPERATOR_GTE, $dateFrom); + $filters[] = new Filter(self::OPERATOR_LTE, $dateTo); break; case self::OPERATOR_NEQ: - $filters[] = new Filter(self::OPERATOR_LT, $dateFrom); - $filters[] = new Filter(self::OPERATOR_GT, $dateTo); + $filters[] = new Filter(self::OPERATOR_LT, $dateFrom); + $filters[] = new Filter(self::OPERATOR_GT, $dateTo); $this->setDataJunction(self::DATA_DISJUNCTION); break; case self::OPERATOR_LT: case self::OPERATOR_GTE: - $filters[] = new Filter($filter->getOperator(), $dateFrom); + $filters[] = new Filter($filter->getOperator(), $dateFrom); break; case self::OPERATOR_GT: case self::OPERATOR_LTE: - $filters[] = new Filter($filter->getOperator(), $dateTo); + $filters[] = new Filter($filter->getOperator(), $dateTo); break; default: $filters[] = $filter; } - }else { + } else { $filters[] = $filter; } } diff --git a/Grid/Column/DateTimeColumn.php b/Grid/Column/DateTimeColumn.php index e7e56aed..b4291bee 100644 --- a/Grid/Column/DateTimeColumn.php +++ b/Grid/Column/DateTimeColumn.php @@ -24,12 +24,19 @@ class DateTimeColumn extends Column protected $fallbackFormat = 'Y-m-d H:i:s'; + protected $inputFormat; + + protected $fallbackInputFormat = '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', array( + $this->setInputFormat($this->getParam('inputFormat', $this->fallbackInputFormat)); + $this->setOperators($this->getParam('operators', [ self::OPERATOR_EQ, self::OPERATOR_NEQ, self::OPERATOR_LT, @@ -40,29 +47,30 @@ public function __initialize(array $params) 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, array($this, "isDateTime")); + $result = array_filter((array) $query, [$this, 'isDateTime']); return !empty($result); } protected function isDateTime($query) { - return strtotime($query) !== false; + return false !== \DateTime::createFromFormat($this->inputFormat, $query); } public function getFilters($source) { $parentFilters = parent::getFilters($source); - $filters = array(); - foreach($parentFilters as $filter) { - $filters[] = ($filter->getValue() === null) ? $filter : $filter->setValue(new \DateTime($filter->getValue())); + $filters = []; + foreach ($parentFilters as $filter) { + $filters[] = ($filter->getValue() === null) ? $filter : $filter->setValue(\DateTime::createFromFormat($this->inputFormat, $filter->getValue())); } return $filters; @@ -71,7 +79,7 @@ public function getFilters($source) public function renderCell($value, $row, $router) { $value = $this->getDisplayedValue($value); - + if (is_callable($this->callback)) { $value = call_user_func($this->callback, $value, $row, $router); } @@ -82,20 +90,20 @@ public function renderCell($value, $row, $router) public function getDisplayedValue($value) { if (!empty($value)) { - $dateTime = $this->getDatetime($value, new \DateTimeZone(date_default_timezone_get())); + $dateTime = $this->getDatetime($value, new \DateTimeZone($this->getTimezone())); if (isset($this->format)) { $value = $dateTime->format($this->format); } else { try { - $transformer = new DateTimeToLocalizedStringTransformer(null, null, $this->dateFormat, $this->timeFormat); + $transformer = new DateTimeToLocalizedStringTransformer(null, $this->getTimezone(), $this->dateFormat, $this->timeFormat); $value = $transformer->transform($dateTime); } catch (\Exception $e) { $value = $dateTime->format($this->fallbackFormat); } } - if (key_exists((string)$value, $this->values)) { + if (array_key_exists((string) $value, $this->values)) { $value = $this->values[$value]; } @@ -106,21 +114,22 @@ public function getDisplayedValue($value) } /** - * DateTimeHelper::getDatetime() from SonataIntlBundle + * DateTimeHelper::getDatetime() from SonataIntlBundle. + * + * @param \Datetime|\DateTimeImmutable|string|int $data + * @param \DateTimeZone timezone * - * @param \Datetime|string|integer $data - * @param null|string timezone * @return \Datetime */ - protected function getDatetime($data, $timezone = null) + protected function getDatetime($data, \DateTimeZone $timezone) { - if($data instanceof \DateTime) { - return $data; + 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; + $data = (int) $data; } if (is_string($data)) { @@ -133,7 +142,7 @@ protected function getDatetime($data, $timezone = null) } // Mongodb bug ? timestamp value is on the key 'i' instead of the key 't' - if (is_array($data) && array_keys($data) == array('t','i')) { + if (is_array($data) && array_keys($data) == ['t', 'i']) { $data = $data['i']; } @@ -156,6 +165,28 @@ public function getFormat() return $this->format; } + public function setInputFormat($inputFormat) + { + $this->inputFormat = $inputFormat; + + return $this; + } + + public function getInputFormat() + { + return $this->inputFormat; + } + + public function getTimezone() + { + return $this->timezone; + } + + public function setTimezone($timezone) + { + $this->timezone = $timezone; + } + public function getType() { return 'datetime'; diff --git a/Grid/Column/JoinColumn.php b/Grid/Column/JoinColumn.php index d5a2a0d4..3cfe820b 100644 --- a/Grid/Column/JoinColumn.php +++ b/Grid/Column/JoinColumn.php @@ -14,7 +14,7 @@ class JoinColumn extends TextColumn { - protected $joinColumns = array(); + protected $joinColumns = []; protected $dataJunction = self::DATA_DISJUNCTION; @@ -22,24 +22,26 @@ public function __initialize(array $params) { parent::__initialize($params); - $this->setJoinColumns($this->getParam('columns', array())); + $this->setJoinColumns($this->getParam('columns', [])); $this->setSeparator($this->getParam('separator', ' ')); $this->setVisibleForSource(true); $this->setIsManualField(true); } - public function setJoinColumns(array $columns) { + public function setJoinColumns(array $columns) + { $this->joinColumns = $columns; } - public function getJoinColumns() { + public function getJoinColumns() + { return $this->joinColumns; } public function getFilters($source) { - $filters = array(); + $filters = []; // Apply same filters on each column foreach ($this->joinColumns as $columnName) { diff --git a/Grid/Column/MassActionColumn.php b/Grid/Column/MassActionColumn.php index e873089c..b8cabc0b 100644 --- a/Grid/Column/MassActionColumn.php +++ b/Grid/Column/MassActionColumn.php @@ -14,19 +14,19 @@ class MassActionColumn extends Column { - const ID = '__action'; + public const ID = '__action'; public function __construct() { - parent::__construct(array( + parent::__construct([ 'id' => self::ID, 'title' => '', 'size' => 15, 'filterable' => true, 'sortable' => false, 'source' => false, - 'align' => 'center' - )); + 'align' => Column::ALIGN_CENTER, + ]); } public function isVisible($isExported = false) diff --git a/Grid/Column/NumberColumn.php b/Grid/Column/NumberColumn.php index dbb6d42d..75cbc799 100644 --- a/Grid/Column/NumberColumn.php +++ b/Grid/Column/NumberColumn.php @@ -16,7 +16,7 @@ class NumberColumn extends Column { - protected static $styles = array( + protected static $styles = [ 'decimal' => \NumberFormatter::DECIMAL, 'percent' => \NumberFormatter::PERCENT, 'money' => \NumberFormatter::CURRENCY, @@ -24,7 +24,7 @@ class NumberColumn extends Column 'duration' => \NumberFormatter::DURATION, 'scientific' => \NumberFormatter::SCIENTIFIC, 'spellout' => \NumberFormatter::SPELLOUT, - ); + ]; protected $style; @@ -48,7 +48,7 @@ public function __initialize(array $params) { parent::__initialize($params); - $this->setAlign($this->getParam('align', 'right')); + $this->setAlign($this->getParam('align', Column::ALIGN_RIGHT)); $this->setStyle($this->getParam('style', 'decimal')); $this->setLocale($this->getParam('locale', \Locale::getDefault())); $this->setPrecision($this->getParam('precision', null)); @@ -63,7 +63,7 @@ public function __initialize(array $params) $this->setRuleSet($this->getParam('ruleSet', '%in-numerals')); // or '%with-words' } - $this->setOperators($this->getParam('operators', array( + $this->setOperators($this->getParam('operators', [ self::OPERATOR_EQ, self::OPERATOR_NEQ, self::OPERATOR_LT, @@ -74,13 +74,13 @@ public function __initialize(array $params) self::OPERATOR_BTWE, self::OPERATOR_ISNULL, self::OPERATOR_ISNOTNULL, - ))); + ])); $this->setDefaultOperator($this->getParam('defaultOperator', self::OPERATOR_EQ)); } public function isQueryValid($query) { - $result = array_filter((array) $query, "is_numeric"); + $result = array_filter((array) $query, 'is_numeric'); return !empty($result); } @@ -108,7 +108,7 @@ public function getDisplayedValue($value) $formatter->setTextAttribute(\NumberFormatter::DEFAULT_RULESET, $this->ruleSet); } - if($this->maxFractionDigits !== null){ + if ($this->maxFractionDigits !== null) { $formatter->setAttribute(\NumberFormatter::MAX_FRACTION_DIGITS, $this->maxFractionDigits); } @@ -136,7 +136,7 @@ public function getDisplayedValue($value) throw new TransformationFailedException($formatter->getErrorMessage()); } - if (key_exists((string)$value, $this->values)) { + if (array_key_exists((string) $value, $this->values)) { $value = $this->values[$value]; } @@ -150,8 +150,8 @@ public function getFilters($source) { $parentFilters = parent::getFilters($source); - $filters = array(); - foreach($parentFilters as $filter) { + $filters = []; + foreach ($parentFilters as $filter) { // Transforme in number for ODM $filters[] = ($filter->getValue() === null) ? $filter : $filter->setValue($filter->getValue() + 0); } diff --git a/Grid/Column/RankColumn.php b/Grid/Column/RankColumn.php index 4da3b3db..a735cacf 100644 --- a/Grid/Column/RankColumn.php +++ b/Grid/Column/RankColumn.php @@ -35,4 +35,12 @@ public function getType() { return 'rank'; } + + /** + * Get the value of rank + */ + public function getRank() + { + return $this->rank; + } } diff --git a/Grid/Column/SimpleArrayColumn.php b/Grid/Column/SimpleArrayColumn.php new file mode 100644 index 00000000..762a3930 --- /dev/null +++ b/Grid/Column/SimpleArrayColumn.php @@ -0,0 +1,93 @@ + + * (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; + +class SimpleArrayColumn extends Column +{ + public function __initialize(array $params) + { + parent::__initialize($params); + + $this->setOperators($this->getParam('operators', [ + self::OPERATOR_LIKE, + self::OPERATOR_NLIKE, + self::OPERATOR_EQ, + self::OPERATOR_NEQ, + self::OPERATOR_ISNULL, + self::OPERATOR_ISNOTNULL, + ])); + $this->setDefaultOperator($this->getParam('defaultOperator', self::OPERATOR_LIKE)); + } + + public function getFilters($source) + { + $parentFilters = parent::getFilters($source); + + $filters = []; + foreach ($parentFilters as $filter) { + switch ($filter->getOperator()) { + case self::OPERATOR_EQ: + case self::OPERATOR_NEQ: + $value = $filter->getValue(); + $filters[] = new Filter($filter->getOperator(), $value); + break; + case self::OPERATOR_LIKE: + case self::OPERATOR_NLIKE: + $value = $filter->getValue(); + $filters[] = new Filter($filter->getOperator(), $value); + break; + case self::OPERATOR_ISNULL: + $filters[] = new Filter(self::OPERATOR_ISNULL); + $filters[] = new Filter(self::OPERATOR_EQ, ''); + $this->setDataJunction(self::DATA_DISJUNCTION); + break; + case self::OPERATOR_ISNOTNULL: + $filters[] = new Filter(self::OPERATOR_ISNOTNULL); + $filters[] = new Filter(self::OPERATOR_NEQ, ''); + break; + default: + $filters[] = $filter; + } + } + + return $filters; + } + + public function renderCell($values, $row, $router) + { + if (is_callable($this->callback)) { + return call_user_func($this->callback, $values, $row, $router); + } + + // @todo: when it has an array as value? + $return = []; + if (is_array($values) || $values instanceof \Traversable) { + foreach ($values as $key => $value) { + if (!is_array($value) && isset($this->values[(string) $value])) { + $value = $this->values[$value]; + } + + $return[$key] = $value; + } + } + + return $return; + } + + public function getType() + { + return 'simple_array'; + } +} diff --git a/Grid/Column/TextColumn.php b/Grid/Column/TextColumn.php index 786d1578..852a0154 100644 --- a/Grid/Column/TextColumn.php +++ b/Grid/Column/TextColumn.php @@ -18,7 +18,7 @@ class TextColumn extends Column { public function isQueryValid($query) { - $result = array_filter((array) $query, "is_string"); + $result = array_filter((array) $query, 'is_string'); return !empty($result); } @@ -27,17 +27,17 @@ public function getFilters($source) { $parentFilters = parent::getFilters($source); - $filters = array(); - foreach($parentFilters as $filter) { + $filters = []; + foreach ($parentFilters as $filter) { switch ($filter->getOperator()) { case self::OPERATOR_ISNULL: - $filters[] = new Filter(self::OPERATOR_ISNULL); - $filters[] = new Filter(self::OPERATOR_EQ, ''); + $filters[] = new Filter(self::OPERATOR_ISNULL); + $filters[] = new Filter(self::OPERATOR_EQ, ''); $this->setDataJunction(self::DATA_DISJUNCTION); break; case self::OPERATOR_ISNOTNULL: - $filters[] = new Filter(self::OPERATOR_ISNOTNULL); - $filters[] = new Filter(self::OPERATOR_NEQ, ''); + $filters[] = new Filter(self::OPERATOR_ISNOTNULL); + $filters[] = new Filter(self::OPERATOR_NEQ, ''); break; default: $filters[] = $filter; diff --git a/Grid/Columns.php b/Grid/Columns.php index a5ed832f..28e0acfd 100644 --- a/Grid/Columns.php +++ b/Grid/Columns.php @@ -12,66 +12,92 @@ 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\SecurityContextInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; class Columns implements \IteratorAggregate, \Countable { - protected $columns = array(); - protected $extensions = array(); + protected $columns = []; + protected $extensions = []; + + public const MISSING_COLUMN_EX_MSG = 'Column with id "%s" doesn\'t exists'; /** - * @var \Symfony\Component\Security\Core\SecurityContextInterface + * @var AuthorizationCheckerInterface */ - protected $securityContext; + protected $authorizationChecker; - public function __construct(SecurityContextInterface $securityContext) + /** + * @param AuthorizationCheckerInterface $authorizationChecker + */ + public function __construct(AuthorizationCheckerInterface $authorizationChecker) { - $this->securityContext = $securityContext; + $this->authorizationChecker = $authorizationChecker; } - public function getIterator($showOnlySourceColumns = false) + /** + * @param bool $showOnlySourceColumns + * + * @return ColumnsIterator + */ + public function getIterator($showOnlySourceColumns = false): ColumnsIterator { return new ColumnsIterator(new \ArrayIterator($this->columns), $showOnlySourceColumns); } /** - * Add column + * Add column. + * * @param Column $column - * @param int $position + * @param int $position + * * @return Columns */ public function addColumn(Column $column, $position = 0) { - $column->setSecurityContext($this->securityContext); + $column->setAuthorizationChecker($this->authorizationChecker); if ($position == 0) { $this->columns[] = $column; } else { if ($position > 0) { - $position--; + --$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, array($column), $tail); + $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('Column with id "%s" doesn\'t exists', $columnId)); + 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) { @@ -83,6 +109,11 @@ public function hasColumnById($columnId, $returnColumn = false) return false; } + /** + * @throws \InvalidArgumentException + * + * @return Column + */ public function getPrimaryColumn() { foreach ($this->columns as $column) { @@ -94,11 +125,19 @@ public function getPrimaryColumn() throw new \InvalidArgumentException('Primary column doesn\'t exists'); } - public function count() + /** + * @return int + */ + public function count(): int { return count($this->columns); } + /** + * @param $extension + * + * @return Columns + */ public function addExtension($extension) { $this->extensions[strtolower($extension->getType())] = $extension; @@ -106,16 +145,30 @@ public function addExtension($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 = ''; @@ -129,17 +182,17 @@ public function getHash() /** * 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 + * placed after if keepOtherColumns is true. * * @param array $columnIds - * @param boolean $keepOtherColumns + * @param bool $keepOtherColumns * - * @return self + * @return Columns */ public function setColumnsOrder(array $columnIds, $keepOtherColumns = true) { - $reorderedColumns = array(); - $columnsIndexedByIds = array(); + $reorderedColumns = []; + $columnsIndexedByIds = []; foreach ($this->columns as $column) { $columnsIndexedByIds[$column->getId()] = $column; @@ -152,12 +205,28 @@ public function setColumnsOrder(array $columnIds, $keepOtherColumns = true) } } - if ($keepOtherColumns) { - $this->columns = array_merge($reorderedColumns, array_values($columnsIndexedByIds)); - } else { - $this->columns = $reorderedColumns; - } - + if ($keepOtherColumns) { + $this->columns = [...$reorderedColumns, ...array_values($columnsIndexedByIds)]; + } else { + $this->columns = $reorderedColumns; + } + return $this; } + + /** + * Get the value of columns + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Get the value of extensions + */ + public function getExtensions() + { + return $this->extensions; + } } diff --git a/Grid/Exception/ColumnAlreadyExistsException.php b/Grid/Exception/ColumnAlreadyExistsException.php new file mode 100644 index 00000000..b4673335 --- /dev/null +++ b/Grid/Exception/ColumnAlreadyExistsException.php @@ -0,0 +1,21 @@ +delimiter = isset($params['delimiter']) ? $params['delimiter'] : $this->delimiter; $this->withBOM = isset($params['withBOM']) ? $params['withBOM'] : $this->withBOM; @@ -35,12 +35,12 @@ public function __construct($title, $fileName = 'export', $params = array(), $ch parent::__construct($title, $fileName, $params, $charset); } - public function computeData($grid) + public function computeData(Grid $grid) { $data = $this->getFlatGridData($grid); // Array to dsv - $outstream = fopen("php://temp", 'r+'); + $outstream = fopen('php://temp', 'r+'); foreach ($data as $line) { fputcsv($outstream, $line, $this->delimiter, '"'); @@ -60,7 +60,7 @@ public function computeData($grid) } /** - * get delimiter + * get delimiter. * * @return string */ @@ -70,9 +70,9 @@ public function getDelimiter() } /** - * set delimiter + * set delimiter. * - * @param string $separator + * @param string $delimiter * * @return self */ @@ -84,7 +84,7 @@ public function setDelimiter($delimiter) } /** - * get BOM setting + * get BOM setting. * * @return string */ @@ -93,8 +93,7 @@ public function getWithBOM() return $this->withBOM; } - /** - * set BOM setting + /*** set BOM setting. * * @param string $withBOM * diff --git a/Grid/Export/ExcelExport.php b/Grid/Export/ExcelExport.php index 916dde18..bc54250b 100644 --- a/Grid/Export/ExcelExport.php +++ b/Grid/Export/ExcelExport.php @@ -12,8 +12,10 @@ namespace APY\DataGridBundle\Grid\Export; +use APY\DataGridBundle\Grid\Grid; + /** - * Excel (This export produces a warning with new Office Excel) + * Excel (This export produces a warning with new Office Excel). */ class ExcelExport extends Export { @@ -21,7 +23,7 @@ class ExcelExport extends Export protected $mimeType = 'application/vnd.ms-excel'; - public function computeData($grid) + public function computeData(Grid $grid) { $data = $this->getGridData($grid); @@ -29,7 +31,7 @@ public function computeData($grid) if (isset($data['titles'])) { $this->content .= ''; foreach ($data['titles'] as $title) { - $this->content .= sprintf("%s", htmlentities($title, ENT_QUOTES)); + $this->content .= sprintf('%s', htmlentities($title, ENT_QUOTES)); } $this->content .= ''; } @@ -37,7 +39,7 @@ public function computeData($grid) foreach ($data['rows'] as $row) { $this->content .= ''; foreach ($row as $cell) { - $this->content .= sprintf("%s", htmlentities($cell, ENT_QUOTES)); + $this->content .= sprintf('%s', htmlentities($cell, ENT_QUOTES)); } $this->content .= ''; } diff --git a/Grid/Export/Export.php b/Grid/Export/Export.php index 4f5d5b91..b4c058e7 100644 --- a/Grid/Export/Export.php +++ b/Grid/Export/Export.php @@ -12,13 +12,16 @@ namespace APY\DataGridBundle\Grid\Export; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use APY\DataGridBundle\Grid\Column\ArrayColumn; +use APY\DataGridBundle\Grid\Grid; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Response; +use Twig\TemplateWrapper; +use Twig\Environment; abstract class Export implements ExportInterface, ContainerAwareInterface { - const DEFAULT_TEMPLATE = 'APYDataGridBundle::blocks.html.twig'; + const DEFAULT_TEMPLATE = '@APYDataGrid/blocks.html.twig'; protected $title; @@ -28,7 +31,7 @@ abstract class Export implements ExportInterface, ContainerAwareInterface protected $mimeType = 'application/octet-stream'; - protected $parameters = array(); + protected $parameters = []; protected $container; @@ -38,7 +41,7 @@ abstract class Export implements ExportInterface, ContainerAwareInterface protected $grid; - protected $params = array(); + protected $params = []; protected $content; @@ -47,17 +50,17 @@ abstract class Export implements ExportInterface, ContainerAwareInterface protected $role; /** - * Default Export constructor + * Default Export constructor. * - * @param string $title Title of the export + * @param string $title Title of the export * @param string $fileName FileName of the export - * @param array $parameters Additionnal parameters for the export - * @param string $charset Charset of the exported data - * @param string $role Security role + * @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 = array(), $charset = 'UTF-8', $role = null) + public function __construct($title, $fileName = 'export', $params = [], $charset = 'UTF-8', $role = null) { $this->title = $title; $this->fileName = $fileName; @@ -70,16 +73,20 @@ public function __construct($title, $fileName = 'export', $params = array(), $ch * 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; } + public function setTwig(Environment $twig) + { + $this->twig = $twig; + } /** * gets the Container associated with this Controller. * @@ -91,11 +98,11 @@ public function getContainer() } /** - * gets the export Response + * gets the export Response. * * @return Response */ - public function getResponse() + public function getResponse() : \Symfony\Component\HttpFoundation\Response { // Response $kernelCharset = $this->container->getParameter('kernel.charset'); @@ -107,15 +114,15 @@ public function getResponse() $this->charset = $kernelCharset; } - $headers = array( - 'Content-Description' => 'File Transfer', - 'Content-Type' => $this->getMimeType(), - 'Content-Disposition' => sprintf('attachment; filename="%s"', $this->getBaseName()), + $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 - ); + 'Cache-Control' => 'must-revalidate', + 'Pragma' => 'public', + 'Content-Length' => $filesize, + ]; $response = new Response($this->content, 200, $headers); $response->setCharset($this->charset); @@ -125,7 +132,7 @@ public function getResponse() } /** - * sets the Content of the export + * sets the Content of the export. * * @param string $content * @@ -139,7 +146,7 @@ public function setContent($content = '') } /** - * gets the Content of the export + * gets the Content of the export. * * @return string */ @@ -149,7 +156,7 @@ public function getContent() } /** - * Get data form the grid + * Get data form the grid. * * @param Grid $grid * @@ -174,7 +181,7 @@ public function getContent() */ protected function getGridData($grid) { - $result = array(); + $result = []; $this->grid = $grid; @@ -189,7 +196,7 @@ protected function getGridData($grid) protected function getRawGridData($grid) { - $result = array(); + $result = []; $this->grid = $grid; if ($this->grid->isTitleSectionVisible()) { @@ -202,7 +209,7 @@ protected function getRawGridData($grid) } /** - * Get data form the grid in a flat array + * Get data form the grid in a flat array. * * @param Grid $grid * @@ -227,7 +234,7 @@ protected function getFlatGridData($grid) { $data = $this->getGridData($grid); - $flatData = array(); + $flatData = []; if (isset($data['titles'])) { $flatData[] = $data['titles']; } @@ -239,7 +246,7 @@ protected function getFlatRawGridData($grid) { $data = $this->getRawGridData($grid); - $flatData = array(); + $flatData = []; if (isset($data['titles'])) { $flatData[] = $data['titles']; } @@ -249,7 +256,7 @@ protected function getFlatRawGridData($grid) protected function getGridTitles() { - $titlesHTML = $this->renderBlock('grid_titles', array('grid' => $this->grid)); + $titlesHTML = $this->renderBlock('grid_titles', ['grid' => $this->grid]); preg_match_all('#]*?>(.*)?#isU', $titlesHTML, $matches); @@ -261,10 +268,11 @@ protected function getGridTitles() new \Exception('Table header (th or td) tags not found.'); } - $titlesClean = array_map(array($this, 'cleanHTML'), $matches[0]); + $titlesClean = array_map([$this, 'cleanHTML'], $matches[0]); $i = 0; - $titles = array(); + $titles = []; + foreach ($this->grid->getColumns() as $column) { if ($column->isVisible(true)) { if (!isset($titlesClean[$i])) { @@ -281,10 +289,10 @@ protected function getRawGridTitles() { $translator = $this->container->get('translator'); - $titles = array(); + $titles = []; foreach ($this->grid->getColumns() as $column) { if ($column->isVisible(true)) { - $titles[] = utf8_decode($translator->trans(/** @Ignore */$column->getTitle())); + $titles[] = utf8_decode($translator->trans(/* @Ignore */$column->getTitle())); } } @@ -293,7 +301,7 @@ protected function getRawGridTitles() protected function getGridRows() { - $rows = array(); + $rows = []; foreach ($this->grid->getRows() as $i => $row) { foreach ($this->grid->getColumns() as $column) { if ($column->isVisible(true)) { @@ -308,7 +316,7 @@ protected function getGridRows() protected function getRawGridRows() { - $rows = array(); + $rows = []; foreach ($this->grid->getRows() as $i => $row) { foreach ($this->grid->getColumns() as $column) { if ($column->isVisible(true)) { @@ -322,53 +330,65 @@ protected function getRawGridRows() protected function getGridCell($column, $row) { + $values = $row->getField($column->getId()); + // Cast a datetime won't work. - if (!is_array($values = $row->getField($column->getId()))) { - $values = array($values); + if ($column instanceof ArrayColumn || !is_array($values)) { + $values = [$values]; } + $separator = $column->getSeparator(); + $block = null; - $return = array(); + $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')) - { - $return[] = $this->renderBlock($block, array('grid' => $this->grid, 'column' => $column, 'row' => $row, 'value' => $value, 'sourceValue' => $sourceValue)); + || $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 { - $return[] = $this->renderBlock('grid_column_cell', array('grid' => $this->grid, 'column' => $column, 'row' => $row, 'value' => $value, 'sourceValue' => $sourceValue)); + $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; } - return implode($column->getSeparator() , $return); + $value = implode($separator, $return); + + return $value; } /** - * Has block + * Has block. * * @param $name string - * @return boolean + * + * @return bool */ protected function hasBlock($name) { foreach ($this->getTemplates() as $template) { - if ($template->hasBlock($name)) { + if ($template->hasBlock($name, [])) { return true; } } @@ -377,16 +397,17 @@ protected function hasBlock($name) } /** - * Render block + * Render block. * * @param $name string * @param $parameters string + * * @return string */ protected function renderBlock($name, $parameters) { foreach ($this->getTemplates() as $template) { - if ($template->hasBlock($name)) { + if ($template->hasBlock($name, [])) { return $template->renderBlock($name, array_merge($parameters, $this->params)); } } @@ -395,10 +416,11 @@ protected function renderBlock($name, $parameters) } /** - * Template Loader + * Template Loader. * - * @return \Twig_TemplateInterface[] * @throws \Exception + * + * @return \Twig_TemplateInterface[] */ protected function getTemplates() { @@ -410,23 +432,25 @@ protected function getTemplates() } /** - * set template + * set template. * * @param \Twig_TemplateInterface|string $template * - * @return self + * @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); + $this->templates[] = $this->twig->load(static::DEFAULT_TEMPLATE); } else { $this->templates = $this->getTemplatesFromString($template); } } elseif ($this->templates === null) { - $this->templates[] = $this->twig->loadTemplate(static::DEFAULT_TEMPLATE); + $this->templates[] = $this->twig->load(static::DEFAULT_TEMPLATE); } else { throw new \Exception('Unable to load template'); } @@ -436,14 +460,11 @@ public function setTemplate($template) protected function getTemplatesFromString($theme) { - $templates = array(); - - $template = $this->twig->loadTemplate($theme); - while ($template != null) { + $templates = []; + $template = $this->twig->load($theme); + if ($template instanceof TemplateWrapper) { $templates[] = $template; - $template = $template->getParent(array()); } - return $templates; } @@ -461,6 +482,12 @@ protected function cleanHTML($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); @@ -468,7 +495,7 @@ protected function cleanHTML($value) } /** - * set title + * set title. * * @param string $title * @@ -482,17 +509,17 @@ public function setTitle($title) } /** - * get title + * get title. * * @return string */ - public function getTitle() + public function getTitle() : string { return $this->title; } /** - * set file name + * set file name. * * @param string $fileName * @@ -506,7 +533,7 @@ public function setFileName($fileName) } /** - * get file name + * get file name. * * @return string */ @@ -516,7 +543,7 @@ public function getFileName() } /** - * set file extension + * set file extension. * * @param string $fileExtension * @@ -530,7 +557,7 @@ public function setFileExtension($fileExtension) } /** - * get file extension + * get file extension. * * @return string */ @@ -540,17 +567,17 @@ public function getFileExtension() } /** - * get base name + * get base name. * * @return string */ public function getBaseName() { - return $this->fileName.(isset($this->fileExtension) ? ".$this->fileExtension" : ''); + return $this->fileName . (isset($this->fileExtension) ? ".$this->fileExtension" : ''); } /** - * set response mime type + * set response mime type. * * @param string $mimeType * @@ -564,7 +591,7 @@ public function setMimeType($mimeType) } /** - * get response mime type + * get response mime type. * * @return string */ @@ -574,7 +601,7 @@ public function getMimeType() } /** - * set response charset + * set response charset. * * @param string $charset * @@ -588,7 +615,7 @@ public function setCharset($charset) } /** - * get response charset + * get response charset. * * @return string */ @@ -598,7 +625,7 @@ public function getCharset() } /** - * set parameters + * set parameters. * * @param array $parameters * @@ -611,8 +638,8 @@ public function setParameters(array $parameters) return $this; } - /** - * get parameters + /** + * get parameters. * * @return array */ @@ -621,8 +648,8 @@ public function getParameters() return $this->parameters; } - /** - * has parameter + /** + * has parameter. * * @return mixed */ @@ -632,11 +659,12 @@ public function hasParameter($name) } /** - * add parameter + * add parameter. * - * @param array $template + * @param $name + * @param $value * - * @return self + * @return \APY\DataGridBundle\Grid\Export\Export */ public function addParameter($name, $value) { @@ -646,13 +674,13 @@ public function addParameter($name, $value) } /** - * get parameter + * get parameter. * * @return mixed */ public function getParameter($name) { - if (!hasParameter($name)) { + if (!$this->hasParameter($name)) { throw new \InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); } @@ -660,7 +688,7 @@ public function getParameter($name) } /** - * set role + * set role. * * @param mixed $role * @@ -674,7 +702,7 @@ public function setRole($role) } /** - * Get role + * Get role. * * @return mixed */ diff --git a/Grid/Export/ExportInterface.php b/Grid/Export/ExportInterface.php index 68de8b7a..8e0b5314 100644 --- a/Grid/Export/ExportInterface.php +++ b/Grid/Export/ExportInterface.php @@ -12,31 +12,34 @@ namespace APY\DataGridBundle\Grid\Export; +use APY\DataGridBundle\Grid\Grid; +use Symfony\Component\HttpFoundation\Response; + interface ExportInterface { /** - * function call by the grid to fill the content of the export + * function call by the grid to fill the content of the export. * * @param Grid $grid The grid */ - public function computeData($grid); + public function computeData(Grid $grid); /** - * Get the export Response + * Get the export Response. * * @return Response */ - public function getResponse(); + public function getResponse(): Response; /** - * Get the export title + * Get the export title. * * @return string */ - public function getTitle(); + public function getTitle(): string; /** - * Get the export role + * Get the export role. * * @return mixed */ diff --git a/Grid/Export/JSONExport.php b/Grid/Export/JSONExport.php index 8397831d..df6e30b7 100644 --- a/Grid/Export/JSONExport.php +++ b/Grid/Export/JSONExport.php @@ -12,14 +12,16 @@ namespace APY\DataGridBundle\Grid\Export; +use APY\DataGridBundle\Grid\Grid; + /** - * JSON + * JSON. */ class JSONExport extends Export { protected $fileExtension = 'json'; - public function computeData($grid) + public function computeData(Grid $grid) { $this->content = json_encode($this->getGridData($grid)); } diff --git a/Grid/Export/PHPExcel2003Export.php b/Grid/Export/PHPExcel2003Export.php index 234737dc..2f5d5804 100644 --- a/Grid/Export/PHPExcel2003Export.php +++ b/Grid/Export/PHPExcel2003Export.php @@ -13,7 +13,7 @@ namespace APY\DataGridBundle\Grid\Export; /** - * PHPExcel_Excel 2003 Export (.xlsx) + * PHPExcel_Excel 2003 Export (.xlsx). */ class PHPExcel2003Export extends PHPExcel2007Export { diff --git a/Grid/Export/PHPExcel2007Export.php b/Grid/Export/PHPExcel2007Export.php index dafcd9d0..eafea087 100644 --- a/Grid/Export/PHPExcel2007Export.php +++ b/Grid/Export/PHPExcel2007Export.php @@ -13,7 +13,7 @@ namespace APY\DataGridBundle\Grid\Export; /** - * PHPExcel 2007 Export + * PHPExcel 2007 Export. */ class PHPExcel2007Export extends PHPExcel5Export { diff --git a/Grid/Export/PHPExcel5Export.php b/Grid/Export/PHPExcel5Export.php index 5ab9f18b..9732be7b 100644 --- a/Grid/Export/PHPExcel5Export.php +++ b/Grid/Export/PHPExcel5Export.php @@ -1,6 +1,6 @@ @@ -12,9 +12,11 @@ namespace APY\DataGridBundle\Grid\Export; +use APY\DataGridBundle\Grid\Grid; + /** * PHPExcel 5 Export (97-2003) (.xls) - * 52 columns maximum + * 52 columns maximum. */ class PHPExcel5Export extends Export { @@ -24,14 +26,14 @@ class PHPExcel5Export extends Export public $objPHPExcel; - public function __construct($tilte, $fileName = 'export', $params = array(), $charset = 'UTF-8') + public function __construct($tilte, $fileName = 'export', $params = [], $charset = 'UTF-8') { - $this->objPHPExcel = new \PHPExcel(); + $this->objPHPExcel = new \PHPExcel(); parent::__construct($tilte, $fileName, $params, $charset); } - public function computeData($grid) + public function computeData(Grid $grid) { $data = $this->getFlatGridData($grid); @@ -39,18 +41,18 @@ public function computeData($grid) foreach ($data as $line) { $column = 'A'; foreach ($line as $cell) { - $this->objPHPExcel->getActiveSheet()->SetCellValue($column.$row, $cell); + $this->objPHPExcel->getActiveSheet()->SetCellValue($column . $row, $cell); - $column++; + ++$column; } - $row++; + ++$row; } $objWriter = $this->getWriter(); ob_start(); - $objWriter->save("php://output"); + $objWriter->save('php://output'); $this->content = ob_get_contents(); diff --git a/Grid/Export/PHPExcelHTMLExport.php b/Grid/Export/PHPExcelHTMLExport.php index 09b6138b..911e1810 100644 --- a/Grid/Export/PHPExcelHTMLExport.php +++ b/Grid/Export/PHPExcelHTMLExport.php @@ -13,7 +13,7 @@ namespace APY\DataGridBundle\Grid\Export; /** - * PHPExcel HTML Export + * PHPExcel HTML Export. */ class PHPExcelHTMLExport extends PHPExcel5Export { diff --git a/Grid/Export/PHPExcelPDFExport.php b/Grid/Export/PHPExcelPDFExport.php index 3d5e964e..c71017d5 100644 --- a/Grid/Export/PHPExcelPDFExport.php +++ b/Grid/Export/PHPExcelPDFExport.php @@ -13,7 +13,7 @@ namespace APY\DataGridBundle\Grid\Export; /** - * PHPExcel PDF Export + * PHPExcel PDF Export. */ class PHPExcelPDFExport extends PHPExcel5Export { diff --git a/Grid/Export/SCSVExport.php b/Grid/Export/SCSVExport.php index ab6c03a3..b0685269 100644 --- a/Grid/Export/SCSVExport.php +++ b/Grid/Export/SCSVExport.php @@ -13,7 +13,7 @@ namespace APY\DataGridBundle\Grid\Export; /** - * Semi-Colon-Separated Values + * Semi-Colon-Separated Values. */ class SCSVExport extends CSVExport { diff --git a/Grid/Export/TSVExport.php b/Grid/Export/TSVExport.php index e41d89d2..463d3c8f 100644 --- a/Grid/Export/TSVExport.php +++ b/Grid/Export/TSVExport.php @@ -13,7 +13,7 @@ namespace APY\DataGridBundle\Grid\Export; /** - * Tab-Separated Values + * Tab-Separated Values. */ class TSVExport extends DSVExport { diff --git a/Grid/Export/XMLExport.php b/Grid/Export/XMLExport.php index 2710f83c..3e325c53 100644 --- a/Grid/Export/XMLExport.php +++ b/Grid/Export/XMLExport.php @@ -12,12 +12,13 @@ namespace APY\DataGridBundle\Grid\Export; -use Symfony\Component\Serializer\Serializer; -use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; +use APY\DataGridBundle\Grid\Grid; use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; +use Symfony\Component\Serializer\Serializer; /** - * XML + * XML. */ class XMLExport extends Export { @@ -25,17 +26,21 @@ class XMLExport extends Export protected $mimeType = 'application/xml'; - public function computeData($grid) + public function computeData(Grid $grid) { - $xmlEncoder = new XmlEncoder(); - $xmlEncoder->setRootNodeName('grid'); - $serializer = new Serializer(array(new GetSetMethodNormalizer()), array('xml' => $xmlEncoder)); + if (defined(XmlEncoder::class.'::ROOT_NODE_NAME')) { + $xmlEncoder = new XmlEncoder([XmlEncoder::ROOT_NODE_NAME => 'grid']); + } else { + $xmlEncoder = new XmlEncoder(); + $xmlEncoder->setRootNodeName('grid'); + } + $serializer = new Serializer([new GetSetMethodNormalizer()], [XmlEncoder::FORMAT => $xmlEncoder]); $data = $this->getGridData($grid); $convertData['titles'] = $data['titles']; $convertData['rows']['row'] = $data['rows']; - $this->content = $serializer->serialize($convertData, 'xml'); + $this->content = $serializer->serialize($convertData, XmlEncoder::FORMAT); } } diff --git a/Grid/Filter.php b/Grid/Filter.php index eaef9fd6..5f189c36 100644 --- a/Grid/Filter.php +++ b/Grid/Filter.php @@ -18,6 +18,11 @@ class Filter protected $operator; protected $columnName; + /** + * @param string $operator + * @param mixed|null $value + * @param string|null $columnName + */ public function __construct($operator, $value = null, $columnName = null) { $this->value = $value; @@ -25,6 +30,11 @@ public function __construct($operator, $value = null, $columnName = null) $this->columnName = $columnName; } + /** + * @param string $operator + * + * @return Filter + */ public function setOperator($operator) { $this->operator = $operator; @@ -32,11 +42,19 @@ public function setOperator($operator) return $this; } + /** + * @return string + */ public function getOperator() { return $this->operator; } + /** + * @param mixed $value + * + * @return Filter + */ public function setValue($value) { $this->value = $value; @@ -44,16 +62,27 @@ public function setValue($value) return $this; } + /** + * @return mixed|null + */ public function getValue() { return $this->value; } + /** + * @return bool + */ public function hasColumnName() { return $this->columnName !== null; } + /** + * @param string $columnName + * + * @return Filter + */ public function setColumnName($columnName) { $this->columnName = $columnName; @@ -61,6 +90,9 @@ public function setColumnName($columnName) return $this; } + /** + * @return string|null + */ public function getColumnName() { return $this->columnName; diff --git a/Grid/Grid.php b/Grid/Grid.php index f7a52731..e59949ef 100644 --- a/Grid/Grid.php +++ b/Grid/Grid.php @@ -13,32 +13,59 @@ namespace APY\DataGridBundle\Grid; -use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\DependencyInjection\ContainerAwareInterface; - use APY\DataGridBundle\Grid\Action\MassActionInterface; use APY\DataGridBundle\Grid\Action\RowActionInterface; +use APY\DataGridBundle\Grid\Column\ActionsColumn; use APY\DataGridBundle\Grid\Column\Column; use APY\DataGridBundle\Grid\Column\MassActionColumn; -use APY\DataGridBundle\Grid\Column\ActionsColumn; -use APY\DataGridBundle\Grid\Source\Source; +use APY\DataGridBundle\Grid\Export\ContainerAwareInterface; +use APY\DataGridBundle\Grid\Export\Export; use APY\DataGridBundle\Grid\Export\ExportInterface; +use APY\DataGridBundle\Grid\Source\Entity; +use APY\DataGridBundle\Grid\Source\Source; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Twig\TemplateWrapper; +use Twig\Environment ; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; -class Grid +class Grid implements GridInterface { - const REQUEST_QUERY_MASS_ACTION_ALL_KEYS_SELECTED = '__action_all_keys'; - const REQUEST_QUERY_MASS_ACTION = '__action_id'; - const REQUEST_QUERY_EXPORT = '__export_id'; - const REQUEST_QUERY_TWEAK = '__tweak_id'; - const REQUEST_QUERY_PAGE = '_page'; - const REQUEST_QUERY_LIMIT = '_limit'; - const REQUEST_QUERY_ORDER = '_order'; - const REQUEST_QUERY_TEMPLATE = '_template'; - const REQUEST_QUERY_RESET = '_reset'; - - /** - * @var \Symfony\Component\DependencyInjection\Container + public const REQUEST_QUERY_MASS_ACTION_ALL_KEYS_SELECTED = '__action_all_keys'; + public const REQUEST_QUERY_MASS_ACTION = '__action_id'; + public const REQUEST_QUERY_EXPORT = '__export_id'; + public const REQUEST_QUERY_TWEAK = '__tweak_id'; + public const REQUEST_QUERY_PAGE = '_page'; + public const REQUEST_QUERY_LIMIT = '_limit'; + public const REQUEST_QUERY_ORDER = '_order'; + public const REQUEST_QUERY_TEMPLATE = '_template'; + public const REQUEST_QUERY_RESET = '_reset'; + + public const SOURCE_ALREADY_SETTED_EX_MSG = 'The source of the grid is already set.'; + public const SOURCE_NOT_SETTED_EX_MSG = 'The source of the grid must be set.'; + public const TWEAK_MALFORMED_ID_EX_MSG = 'Tweak id "%s" is malformed. The id have to match this regex ^[0-9a-zA-Z_\+-]+'; + public const TWIG_TEMPLATE_LOAD_EX_MSG = 'Unable to load template'; + public const NOT_VALID_LIMIT_EX_MSG = 'Limit has to be array or integer'; + public const NOT_VALID_PAGE_NUMBER_EX_MSG = 'Page must be a positive number'; + public const NOT_VALID_MAX_RESULT_EX_MSG = 'Max results must be a positive number.'; + public const MASS_ACTION_NOT_DEFINED_EX_MSG = 'Action %s is not defined.'; + public const MASS_ACTION_CALLBACK_NOT_VALID_EX_MSG = 'Callback %s is not callable or Controller action'; + public const EXPORT_NOT_DEFINED_EX_MSG = 'Export %s is not defined.'; + public const PAGE_NOT_VALID_EX_MSG = 'Page must be a positive number'; + public const COLUMN_ORDER_NOT_VALID_EX_MSG = '%s is not a valid order.'; + public const DEFAULT_LIMIT_NOT_VALID_EX_MSG = 'Limit must be a positive number'; + public const LIMIT_NOT_DEFINED_EX_MSG = 'Limit %s is not defined in limits.'; + public const NO_ROWS_RETURNED_EX_MSG = 'Source have to return Rows object.'; + public const INVALID_TOTAL_COUNT_EX_MSG = 'Source function getTotalCount need to return integer result, returned: %s'; + public const NOT_VALID_TWEAK_ID_EX_MSG = 'Tweak with id "%s" doesn\'t exists'; + public const GET_FILTERS_NO_REQUEST_HANDLED_EX_MSG = 'getFilters method is only available in the manipulate callback function or after the call of the method isRedirected of the grid.'; + public const HAS_FILTER_NO_REQUEST_HANDLED_EX_MSG = 'hasFilters method is only available in the manipulate callback function or after the call of the method isRedirected of the grid.'; + public const TWEAK_NOT_DEFINED_EX_MSG = 'Tweak %s is not defined.'; + + /** + * @var Container */ protected $container; @@ -53,14 +80,13 @@ class Grid protected $session; /** - * @var \Symfony\Component\HttpFoundation\Request + * @var Request */ protected $request; - /** - * @var \Symfony\Component\Security\Core\SecurityContext - */ - protected $securityContext; + protected AuthorizationCheckerInterface $securityContext; + + protected Environment $twig; /** * @var string @@ -82,15 +108,12 @@ class Grid */ protected $routeParameters; - /** - * @var \APY\DataGridBundle\Grid\Source\Source - */ - protected $source; + protected ?Source $source = null; /** - * @var boolean + * @var bool */ - protected $prepared = false; + protected $prepared = false; /** * @var int @@ -110,35 +133,35 @@ class Grid /** * @var array */ - protected $limits = array(); + protected $limits = []; /** - * @var \APY\DataGridBundle\Grid\Columns|\APY\DataGridBundle\Grid\Column\Column[] + * @var Columns|Column[] */ protected $columns; /** - * @var \APY\DataGridBundle\Grid\Rows + * @var Rows */ protected $rows; /** - * @var \APY\DataGridBundle\Grid\Action\MassAction[] + * @var Action\MassAction[] */ - protected $massActions = array(); + protected $massActions = []; /** - * @var \APY\DataGridBundle\Grid\Action\RowAction[] + * @var Action\RowAction[] */ - protected $rowActions = array(); + protected $rowActions = []; /** - * @var boolean + * @var bool */ protected $showFilters = true; /** - * @var boolean + * @var bool */ protected $showTitles = true; @@ -150,7 +173,7 @@ class Grid /** * @var array|object session */ - protected $sessionData; + protected $sessionData = []; /** * @var string @@ -158,12 +181,12 @@ class Grid protected $prefixTitle = ''; /** - * @var boolean + * @var bool */ protected $persistence = false; /** - * @var boolean + * @var bool */ protected $newSession = false; @@ -178,17 +201,17 @@ class Grid protected $noResultMessage; /** - * @var \APY\DataGridBundle\Grid\Export\Export[] + * @var Export[] */ - protected $exports = array(); + protected $exports = []; /** - * @var boolean + * @var bool */ protected $redirect = null; /** - * @var boolean + * @var bool */ protected $isReadyForExport = false; @@ -210,95 +233,111 @@ class Grid /** * @var array */ - protected $items = array(); + protected $items = []; /** - * Data junction of the grid + * Data junction of the grid. * * @var int */ protected $dataJunction = Column::DATA_CONJUNCTION; /** - * Permanent filters + * Permanent filters. * * @var array */ - protected $permanentFilters = array(); + protected $permanentFilters = []; /** - * Default filters + * Default filters. * * @var array */ - protected $defaultFilters = array(); + protected $defaultFilters = []; /** - * Default order (e.g. my_column_id|asc) + * Default order (e.g. my_column_id|asc). * * @var string */ protected $defaultOrder; /** - * Default limit + * Default limit. * - * @var integer + * @var int */ protected $defaultLimit; /** - * Default page + * Default page. * * @var int */ protected $defaultPage; /** - * Tweaks + * Tweaks. * * @var array */ - protected $tweaks = array(); + protected $tweaks = []; /** - * Default Tweak + * Default Tweak. * * @var string */ protected $defaultTweak; /** - * Filters in session + * Filters in session. + * * @var array */ protected $sessionFilters; // Lazy parameters - protected $lazyAddColumn = array(); - protected $lazyHiddenColumns = array(); - protected $lazyVisibleColumns = array(); - protected $lazyHideShowColumns = array(); + protected $lazyAddColumn = []; + protected $lazyHiddenColumns = []; + protected $lazyVisibleColumns = []; + protected $lazyHideShowColumns = []; // Lazy parameters for the action column protected $actionsColumnSize; protected $actionsColumnTitle; /** - * @param \Symfony\Component\DependencyInjection\Container $container - * @param string $id set if you are using more then one grid inside controller + * The grid configuration. + */ + private ?\APY\DataGridBundle\Grid\GridConfigInterface $config; + + /** + * Constructor. + * + * @param Container $container + * @param string $id set if you are using more then one grid inside controller + * @param GridConfigInterface|null $config The grid configuration. */ - public function __construct($container, $id = '') + public function __construct($container, AuthorizationCheckerInterface $securityContext, Environment $twig, $id = '', GridConfigInterface $config = null) { + // @todo: why the whole container is injected? $this->container = $container; + $this->config = $config; $this->router = $container->get('router'); - $this->request = $container->get('request'); + $this->request = $container->get('request_stack')->getCurrentRequest(); $this->session = $this->request->getSession(); - $this->securityContext = $container->get('security.context'); - + $this->securityContext = $securityContext; + $this->twig = $twig; $this->id = $id; + // even id is set, do create hash early + if (!empty($this->id)) { + $this->createHash(); + } + $this->columns = new Columns($this->securityContext); $this->routeParameters = $this->request->attributes->all(); @@ -310,18 +349,141 @@ public function __construct($container, $id = '') } /** - * Sets Source to the Grid + * {@inheritdoc} + */ + public function initialize() + { + if (!$this->config) { + return $this; + } + + $config = $this->config; + + $this->setPersistence($config->isPersisted()); + + // Route parameters + $routeParameters = []; + $parameters = $config->getRouteParameters(); + if (!empty($parameters)) { + $routeParameters = $parameters; + foreach ($routeParameters as $parameter => $value) { + $this->setRouteParameter($parameter, $value); + } + } + + // Route + if (null !== $config->getRoute()) { + $this->setRouteUrl($this->router->generate($config->getRoute(), $routeParameters)); + } + + // Route + if (null !== $config->getRoute()) { + $this->setRouteUrl($this->router->generate($config->getRoute(), $routeParameters)); + } + + // Columns + foreach ($this->lazyAddColumn as $columnInfo) { + /** @var Column $column */ + $column = $columnInfo['column']; + + if (!$config->isFilterable()) { + $column->setFilterable(false); + } + + if (!$config->isSortable()) { + $column->setSortable(false); + } + } + + // Source + $source = $config->getSource(); + + if (null !== $source) { + $this->source = $source; + + $source->initialise($this->container); + + if ($source instanceof Entity) { + $groupBy = $config->getGroupBy(); + if (null !== $groupBy) { + if (!is_array($groupBy)) { + $groupBy = [$groupBy]; + } + + // Must be set after source because initialize method reset groupBy property + $source->setGroupBy($groupBy); + } + } + } + + // Order + if (null !== $config->getSortBy()) { + $this->setDefaultOrder($config->getSortBy(), $config->getOrder()); + } + + if (null !== $config->getMaxPerPage()) { + $this->setLimits($config->getMaxPerPage()); + } + + $this + ->setMaxResults($config->getMaxResults()) + ->setPage($config->getPage()); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(Request $request) + { + if (null === $this->source) { + throw new \LogicException(self::SOURCE_NOT_SETTED_EX_MSG); + } + + $this->request = $request; + $this->session = $request->getSession(); + + $this->createHash(); + + $this->requestData = $request->get($this->hash); + + $this->processPersistence(); + + $this->sessionData = (array) $this->session->get($this->hash); + + $this->processLazyParameters(); + + if (!empty($this->requestData)) { + $this->processRequestData(); + } + + if ($this->newSession) { + $this->setDefaultSessionData(); + } + + $this->processPermanentFilters(); + + $this->processSessionData(); + + $this->prepare(); + + return $this; + } + + /** + * Sets Source to the Grid. * * @param $source * - * @return self - * * @throws \InvalidArgumentException + * + * @return self */ public function setSource(Source $source) { if ($this->source !== null) { - throw new \InvalidArgumentException('The source of the grid is already set.'); + throw new \InvalidArgumentException(self::SOURCE_ALREADY_SETTED_EX_MSG); } $this->source = $source; @@ -334,7 +496,7 @@ public function setSource(Source $source) return $this; } - public function getSource() + public function getSource(): ?Source { return $this->source; } @@ -345,7 +507,7 @@ public function getSource() public function isReadyForRedirect() { if ($this->source === null) { - throw new \Exception('The source of the grid is not set.'); + throw new \Exception(self::SOURCE_NOT_SETTED_EX_MSG); } if ($this->redirect !== null) { @@ -389,16 +551,16 @@ public function isReadyForRedirect() protected function getCurrentUri() { - return $this->request->getScheme().'://'.$this->request->getHttpHost().$this->request->getBaseUrl().$this->request->getPathInfo(); + return $this->request->getScheme() . '://' . $this->request->getHttpHost() . $this->request->getBaseUrl() . $this->request->getPathInfo(); } protected function processPersistence() { - $referer = strtok($this->request->headers->get('referer'), '?'); + $referer = strtok((string) $this->request->headers->get('referer'), '?'); // Persistence or reset - kill previous session if ((!$this->request->isXmlHttpRequest() && !$this->persistence && $referer != $this->getCurrentUri()) - || isset($this->requestData[self::REQUEST_QUERY_RESET])) { + || isset($this->requestData[self::REQUEST_QUERY_RESET])) { $this->session->remove($this->hash); } @@ -421,7 +583,7 @@ protected function processLazyParameters() // Visible columns if (!empty($this->lazyVisibleColumns)) { - $columnNames = array(); + $columnNames = []; foreach ($this->columns as $column) { $columnNames[] = $column->getId(); } @@ -444,7 +606,7 @@ protected function processRequestData() { $this->processMassActions($this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION)); - if ($this->processExports($this->getFromRequest(Grid::REQUEST_QUERY_EXPORT)) + if ($this->processExports($this->getFromRequest(self::REQUEST_QUERY_EXPORT)) || $this->processTweaks($this->getFromRequest(self::REQUEST_QUERY_TWEAK))) { return; } @@ -461,7 +623,7 @@ protected function processRequestData() } /** - * Process mass actions + * Process mass actions. * * @param int $actionId * @@ -473,48 +635,55 @@ protected function processMassActions($actionId) if ($actionId > -1 && '' !== $actionId) { if (array_key_exists($actionId, $this->massActions)) { $action = $this->massActions[$actionId]; - $actionAllKeys = (boolean)$this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION_ALL_KEYS_SELECTED); - $actionKeys = $actionAllKeys == false ? (array) $this->getFromRequest(MassActionColumn::ID) : array(); + $actionAllKeys = (bool) $this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION_ALL_KEYS_SELECTED); + $actionKeys = $actionAllKeys === false ? array_keys((array) $this->getFromRequest(MassActionColumn::ID)) : []; $this->processSessionData(); if ($actionAllKeys) { $this->page = 0; $this->limit = 0; } + $this->prepare(); + if ($actionAllKeys === true) { + foreach ($this->rows as $row) { + $actionKeys[] = $row->getPrimaryFieldValue(); + } + } + if (is_callable($action->getCallback())) { - $this->massActionResponse = call_user_func($action->getCallback(), array_keys($actionKeys), $actionAllKeys, $this->session, $action->getParameters()); + $this->massActionResponse = call_user_func($action->getCallback(), $actionKeys, $actionAllKeys, $this->session, $action->getParameters()); } elseif (strpos($action->getCallback(), ':') !== false) { $path = array_merge( - array( - 'primaryKeys' => array_keys($actionKeys), + [ + 'primaryKeys' => $actionKeys, 'allPrimaryKeys' => $actionAllKeys, - '_controller' => $action->getCallback() - ), + '_controller' => $action->getCallback(), + ], $action->getParameters() ); - $subRequest = $this->container->get('request')->duplicate(array(), null, $path); + $subRequest = $this->request->duplicate([], null, $path); $this->massActionResponse = $this->container->get('http_kernel')->handle($subRequest, \Symfony\Component\HttpKernel\HttpKernelInterface::SUB_REQUEST); } else { - throw new \RuntimeException(sprintf('Callback %s is not callable or Controller action', $action->getCallback())); + throw new \RuntimeException(sprintf(self::MASS_ACTION_CALLBACK_NOT_VALID_EX_MSG, $action->getCallback())); } } else { - throw new \OutOfBoundsException(sprintf('Action %s is not defined.', $actionId)); + throw new \OutOfBoundsException(sprintf(self::MASS_ACTION_NOT_DEFINED_EX_MSG, $actionId)); } } } /** - * Process exports + * Process exports. * * @param int $exportId * - * @return boolean - * * @throws \OutOfBoundsException + * + * @return bool */ protected function processExports($exportId) { @@ -530,6 +699,7 @@ protected function processExports($exportId) $export = $this->exports[$exportId]; if ($export instanceof ContainerAwareInterface) { $export->setContainer($this->container); + $export->setTwig($this->twig); } $export->computeData($this); @@ -537,7 +707,7 @@ protected function processExports($exportId) return true; } else { - throw new \OutOfBoundsException(sprintf('Export %s is not defined.', $exportId)); + throw new \OutOfBoundsException(sprintf(self::EXPORT_NOT_DEFINED_EX_MSG, $exportId)); } } @@ -545,28 +715,28 @@ protected function processExports($exportId) } /** - * Process tweaks + * Process tweaks. * * @param int $tweakId * - * @return boolean - * * @throws \OutOfBoundsException + * + * @return bool */ protected function processTweaks($tweakId) { - if ($tweakId != null) { + if ($tweakId !== null) { if (array_key_exists($tweakId, $this->tweaks)) { $tweak = $this->tweaks[$tweakId]; $saveAsActive = false; if (isset($tweak['reset'])) { - $this->sessionData = array(); + $this->sessionData = []; $this->session->remove($this->hash); } if (isset($tweak['filters'])) { - $this->defaultFilters = array(); + $this->defaultFilters = []; $this->setDefaultFilters($tweak['filters']); $this->processDefaultFilters(); $saveAsActive = true; @@ -643,7 +813,7 @@ protected function processTweaks($tweakId) return true; } else { - throw new \OutOfBoundsException(sprintf('Tweak %s is not defined.', $tweakId)); + throw new \OutOfBoundsException(sprintf(self::TWEAK_NOT_DEFINED_EX_MSG, $tweakId)); } } @@ -660,6 +830,17 @@ protected function processRequestFilters() // Get data from request $data = $this->getFromRequest($ColumnId); + //if no item is selectd in multi select filter : simulate empty first choice + if ($column->getFilterType() == 'select' + && $column->getSelectMulti() === true + && $data === null + && $this->getFromRequest(self::REQUEST_QUERY_PAGE) === null + && $this->getFromRequest(self::REQUEST_QUERY_ORDER) === null + && $this->getFromRequest(self::REQUEST_QUERY_LIMIT) === null + && ($this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION) === null || $this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION) == '-1')) { + $data = ['from' => '']; + } + // Store in the session $this->set($ColumnId, $data); @@ -677,9 +858,9 @@ protected function processPage($page, $filtering = false) { // Set to the first page if this is a request of order, limit, mass action or filtering if ($this->getFromRequest(self::REQUEST_QUERY_ORDER) !== null - || $this->getFromRequest(self::REQUEST_QUERY_LIMIT) !== null - || $this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION) !== null - || $filtering) { + || $this->getFromRequest(self::REQUEST_QUERY_LIMIT) !== null + || $this->getFromRequest(self::REQUEST_QUERY_MASS_ACTION) !== null + || $filtering) { $this->set(self::REQUEST_QUERY_PAGE, 0); } else { $this->set(self::REQUEST_QUERY_PAGE, $page); @@ -692,7 +873,7 @@ protected function processOrder($order) list($columnId, $columnOrder) = explode('|', $order); $column = $this->columns->getColumnById($columnId); - if ($column->isSortable() && in_array(strtolower($columnOrder), array('asc', 'desc'))) { + if ($column->isSortable() && in_array(strtolower($columnOrder), ['asc', 'desc'])) { $this->set(self::REQUEST_QUERY_ORDER, $order); } } @@ -715,7 +896,7 @@ protected function setDefaultSessionData() if ((int) $this->defaultPage >= 0) { $this->set(self::REQUEST_QUERY_PAGE, $this->defaultPage); } else { - throw new \InvalidArgumentException('Page must be a positive number'); + throw new \InvalidArgumentException(self::NOT_VALID_PAGE_NUMBER_EX_MSG); } } @@ -724,10 +905,10 @@ protected function setDefaultSessionData() list($columnId, $columnOrder) = explode('|', $this->defaultOrder); $this->columns->getColumnById($columnId); - if (in_array(strtolower($columnOrder), array('asc', 'desc'))) { + if (in_array(strtolower($columnOrder), ['asc', 'desc'])) { $this->set(self::REQUEST_QUERY_ORDER, $this->defaultOrder); } else { - throw new \InvalidArgumentException($columnOrder . ' is not a valid order.'); + throw new \InvalidArgumentException(sprintf(self::COLUMN_ORDER_NOT_VALID_EX_MSG, $columnOrder)); } } @@ -736,10 +917,10 @@ protected function setDefaultSessionData() if (isset($this->limits[$this->defaultLimit])) { $this->set(self::REQUEST_QUERY_LIMIT, $this->defaultLimit); } else { - throw new \InvalidArgumentException(sprintf('Limit %s is not defined in limits.', $this->defaultLimit)); + throw new \InvalidArgumentException(sprintf(self::LIMIT_NOT_DEFINED_EX_MSG, $this->defaultLimit)); } } else { - throw new \InvalidArgumentException('Limit must be a positive number'); + throw new \InvalidArgumentException(self::DEFAULT_LIMIT_NOT_VALID_EX_MSG); } } @@ -750,8 +931,8 @@ protected function setDefaultSessionData() $this->saveSession(); } - /** - * Store permanent filters to the session and disable the filter capability for the column if there are permanent filters + /** + * Store permanent filters to the session and disable the filter capability for the column if there are permanent filters. */ protected function processFilters($permanent = true) { @@ -766,7 +947,7 @@ protected function processFilters($permanent = true) // Convert simple value if (!is_array($value) || !is_string(key($value))) { - $value = array('from' => $value); + $value = ['from' => $value]; } // Convert boolean value @@ -777,11 +958,11 @@ protected function processFilters($permanent = true) // Convert simple value with select filter if ($column->getFilterType() === 'select') { if (isset($value['from']) && !is_array($value['from'])) { - $value['from'] = array($value['from']); + $value['from'] = [$value['from']]; } if (isset($value['to']) && !is_array($value['to'])) { - $value['to'] = array($value['to']); + $value['to'] = [$value['to']]; } } @@ -836,11 +1017,11 @@ protected function processSessionData() } /** - * Prepare Grid for Drawing - * - * @return self + * Prepare Grid for Drawing. * * @throws \Exception + * + * @return self */ protected function prepare() { @@ -855,7 +1036,7 @@ protected function prepare() } if (!$this->rows instanceof Rows) { - throw new \Exception('Source have to return Rows object.'); + throw new \Exception(self::NO_ROWS_RETURNED_EX_MSG); } if (count($this->rows) == 0 && $this->page > 0) { @@ -892,22 +1073,6 @@ protected function prepare() $row->setPrimaryField($primaryColumnId); } - //@todo refactor autohide titles when no title is set - if (!$this->showTitles) { - $this->showTitles = false; - foreach ($this->columns as $column) { - if (!$this->showTitles) { - break; - } - - if ($column->getTitle() != '') { - $this->showTitles = true; - - break; - } - } - } - //get size if ($this->source->isDataLoaded()) { $this->source->populateSelectFiltersFromData($this->columns); @@ -918,7 +1083,7 @@ protected function prepare() } if (!is_int($this->totalCount)) { - throw new \Exception(sprintf('Source function getTotalCount need to return integer result, returned: %s', gettype($this->totalCount))); + throw new \Exception(sprintf(self::INVALID_TOTAL_COUNT_EX_MSG, gettype($this->totalCount))); } $this->prepared = true; @@ -938,6 +1103,7 @@ protected function getFromRequest($key) if (isset($this->requestData[$key])) { return $this->requestData[$key]; } + return null; } /** @@ -952,18 +1118,21 @@ protected function get($key) if (isset($this->sessionData[$key])) { return $this->sessionData[$key]; } + return null; } /** * Writes data to the session. * - * @param string $key A unique key identifying the data - * @param mixed $data Data associated with the key + * @param string $key A unique key identifying the data + * @param mixed $data Data associated with the key */ protected function set($key, $data) { // Only the filters values are removed from the session - if (isset($data['from']) && ((is_string($data['from']) && $data['from'] === '') || (is_array($data['from']) && $data['from'][0] === ''))) { + $fromIsEmpty = isset($data['from']) && ((is_string($data['from']) && $data['from'] === '') || (is_array($data['from']) && $data['from'][0] === '')); + $toIsSet = isset($data['to']) && (is_string($data['to']) && $data['to'] !== ''); + if ($fromIsEmpty && !$toIsSet) { if (array_key_exists($key, $this->sessionData)) { unset($this->sessionData[$key]); } @@ -974,14 +1143,14 @@ protected function set($key, $data) protected function saveSession() { - if (!empty($this->sessionData)) { + if (!empty($this->sessionData) && !empty($this->hash)) { $this->session->set($this->hash, $this->sessionData); } } protected function createHash() { - $this->hash = 'grid_'. (empty($this->id) ? md5($this->request->get('_controller').$this->columns->getHash().$this->source->getHash()) : $this->getId()); + $this->hash = 'grid_' . (empty($this->id) ? md5($this->request->get('_controller') . $this->columns->getHash() . $this->source->getHash()) : $this->getId()); } public function getHash() @@ -990,7 +1159,7 @@ public function getHash() } /** - * Adds custom column to the grid + * Adds custom column to the grid. * * @param $column * @param int $position @@ -999,13 +1168,13 @@ public function getHash() */ public function addColumn($column, $position = 0) { - $this->lazyAddColumn[] = array('column' => $column, 'position' => $position); + $this->lazyAddColumn[] = ['column' => $column, 'position' => $position]; return $this; } /** - * Get a column by its identifier + * Get a column by its identifier. * * @param $columnId * @@ -1023,9 +1192,9 @@ public function getColumn($columnId) } /** - * Returns Grid Columns + * Returns Grid Columns. * - * @return Column\Column[]|Columns + * @return Column[]|Columns */ public function getColumns() { @@ -1033,10 +1202,11 @@ public function getColumns() } /** - * Returns true if column exists in columns and lazyAddColumn properties + * Returns true if column exists in columns and lazyAddColumn properties. * * @param $columnId - * @return boolean + * + * @return bool */ public function hasColumn($columnId) { @@ -1050,7 +1220,7 @@ public function hasColumn($columnId) } /** - * Sets Array of Columns to the grid + * Sets Array of Columns to the grid. * * @param $columns * @@ -1066,10 +1236,10 @@ public function setColumns(Columns $columns) /** * Sets order of Columns passing an array of column ids * If the list of ids is uncomplete, the remaining columns will be - * placed after + * placed after. * * @param array $columnIds - * @param boolean $keepOtherColumns + * @param bool $keepOtherColumns * * @return self */ @@ -1081,7 +1251,7 @@ public function setColumnsOrder(array $columnIds, $keepOtherColumns = true) } /** - * Adds Mass Action + * Adds Mass Action. * * @param Action\MassActionInterface $action * @@ -1097,7 +1267,7 @@ public function addMassAction(MassActionInterface $action) } /** - * Returns Mass Actions + * Returns Mass Actions. * * @return Action\MassAction[] */ @@ -1107,21 +1277,22 @@ public function getMassActions() } /** - * Add a tweak - * @param string title title of the tweak - * @param array $tweak array('filters' => array, 'order' => 'colomunId|order', 'page' => integer, 'limit' => integer, 'export' => integer, 'massAction' => integer) - * @param string id id of the tweak matching the regex ^[0-9a-zA-Z_\+-]+ - * @param string group group of the tweak + * Add a tweak. + * + * @param string $title title of the tweak + * @param array $tweak array('filters' => array, 'order' => 'colomunId|order', 'page' => integer, 'limit' => integer, 'export' => integer, 'massAction' => integer) + * @param string $id id of the tweak matching the regex ^[0-9a-zA-Z_\+-]+ + * @param string $group group of the tweak * * @return self */ public function addTweak($title, array $tweak, $id = null, $group = null) { if ($id !== null && !preg_match('/^[0-9a-zA-Z_\+-]+$/', $id)) { - throw new \InvalidArgumentException(sprintf('Tweak id "%s" is malformed. The id have to match this regex ^[0-9a-zA-Z_\+-]+', $id)); + throw new \InvalidArgumentException(sprintf(self::TWEAK_MALFORMED_ID_EX_MSG, $id)); } - $tweak = array_merge(array('id' => $id, 'title' => $title, 'group' => $group), $tweak); + $tweak = array_merge(['id' => $id, 'title' => $title, 'group' => $group], $tweak); if (isset($id)) { $this->tweaks[$id] = $tweak; } else { @@ -1133,28 +1304,34 @@ public function addTweak($title, array $tweak, $id = null, $group = null) /** * Returns tweaks - * Add the url of the tweak + * Add the url of the tweak. * * @return array */ public function getTweaks() { - $separator = strpos($this->getRouteUrl(), '?') ? '&' : '?'; - $url = $this->getRouteUrl().$separator.$this->getHash().'['.Grid::REQUEST_QUERY_TWEAK.']='; + $separator = strpos($this->getRouteUrl(), '?') ? '&' : '?'; + $url = $this->getRouteUrl() . $separator . $this->getHash() . '[' . self::REQUEST_QUERY_TWEAK . ']='; foreach ($this->tweaks as $id => $tweak) { - $this->tweaks[$id] = array_merge($tweak, array('url' => $url.$id)); + $this->tweaks[$id] = array_merge($tweak, ['url' => $url . $id]); } return $this->tweaks; } + public function getRawTweaks() + { + return $this->tweaks; + } + public function getActiveTweaks() { return (array) $this->get('tweaks'); } + /** - * Returns a tweak + * Returns a tweak. * * @return array */ @@ -1165,11 +1342,11 @@ public function getTweak($id) return $tweaks[$id]; } - throw new \InvalidArgumentException(sprintf('Tweak with id "%s" doesn\'t exists', $id)); + throw new \InvalidArgumentException(sprintf(self::NOT_VALID_TWEAK_ID_EX_MSG, $id)); } /** - * Returns tweaks with a specific group + * Returns tweaks with a specific group. * * @return array */ @@ -1189,10 +1366,12 @@ public function getTweaksGroup($group) public function getActiveTweakGroup($group) { $tweaks = $this->getActiveTweaks(); + return isset($tweaks[$group]) ? $tweaks[$group] : -1; } + /** - * Adds Row Action + * Adds Row Action. * * @param Action\RowActionInterface $action * @@ -1208,7 +1387,7 @@ public function addRowAction(RowActionInterface $action) } /** - * Returns Row Actions + * Returns Row Actions. * * @return Action\RowAction[] */ @@ -1218,23 +1397,22 @@ public function getRowActions() } /** - * Sets template for export - * - * @param Export $template + * Sets template for export. * - * @return self + * @param TemplateWrapper|string $template * * @throws \Exception + * + * @return self */ public function setTemplate($template) { if ($template !== null) { - if ($template instanceof \Twig_Template) { + if ($template instanceof TemplateWrapper) { $template = '__SELF__' . $template->getTemplateName(); - } elseif (!is_string($template) && $template === null) { - throw new \Exception('Unable to load template'); + } elseif (!is_string($template)) { + throw new \Exception(self::TWIG_TEMPLATE_LOAD_EX_MSG); } - $this->set(self::REQUEST_QUERY_TEMPLATE, $template); $this->saveSession(); } @@ -1243,9 +1421,9 @@ public function setTemplate($template) } /** - * Returns template + * Returns template. * - * @return Twig_Template + * @return string */ public function getTemplate() { @@ -1253,7 +1431,7 @@ public function getTemplate() } /** - * Adds Export + * Adds Export. * * @param ExportInterface $export * @@ -1269,9 +1447,9 @@ public function addExport(ExportInterface $export) } /** - * Returns exports + * Returns exports. * - * @return Export[] + * @return ExportInterface[] */ public function getExports() { @@ -1279,30 +1457,30 @@ public function getExports() } /** - * Returns the export response + * Returns the export response. * - * @return Export[] + * @return Response */ - public function getExportResponse() + public function getExportResponse(): Response { return $this->exportResponse; } /** - * Returns the mass action response + * Returns the mass action response. * - * @return Export[] + * @return Response */ - public function getMassActionResponse() + public function getMassActionResponse(): Response { return $this->massActionResponse; } /** - * Sets Route Parameters + * Sets Route Parameters. * * @param string $parameter - * @param mixed $value + * @param mixed $value * * @return self */ @@ -1314,7 +1492,7 @@ public function setRouteParameter($parameter, $value) } /** - * Returns Route Parameters + * Returns Route Parameters. * * @return array */ @@ -1324,9 +1502,9 @@ public function getRouteParameters() } /** - * Sets Route URL + * Sets Route URL. * - * @param string routeUrl + * @param string $routeUrl * * @return self */ @@ -1338,14 +1516,14 @@ public function setRouteUrl($routeUrl) } /** - * Returns Route URL + * Returns Route URL. * * @return string */ public function getRouteUrl() { if ($this->routeUrl === null) { - $this->routeUrl = $this->router->generate($this->request->get('_route'), $this->getRouteParameters()); + $this->routeUrl = $this->router->generate((string) $this->request->get('_route'), $this->getRouteParameters()); } return $this->routeUrl; @@ -1362,10 +1540,10 @@ public function isMassActionRedirect() } /** - * Set value for filters + * Set value for filters. * - * @param array Hash of columnName => initValue - * @param boolean permanent filters ? + * @param array $filters Hash of columnName => initValue + * @param bool $permanent filters ? * * @return self */ @@ -1383,10 +1561,9 @@ protected function setFilters(array $filters, $permanent = true) } /** - * Set permanent value for filters + * Set permanent value for filters. * - * @param array Hash of columnName => initValue - * @param boolean fixed filters ? + * @param array $filters Hash of columnName => initValue * * @return self */ @@ -1396,9 +1573,9 @@ public function setPermanentFilters(array $filters) } /** - * Set default value for filters + * Set default value for filters. * - * @param array Hash of columnName => initValue + * @param array $filters Hash of columnName => initValue * * @return self */ @@ -1410,7 +1587,7 @@ public function setDefaultFilters(array $filters) /** * Set the default grid order. * - * @param array Hash of columnName => initValue + * @param $columnId * * @return self */ @@ -1423,7 +1600,7 @@ public function setDefaultOrder($columnId, $order) } /** - * Sets unique filter identification + * Sets unique filter identification. * * @param $id * @@ -1437,7 +1614,7 @@ public function setId($id) } /** - * Returns unique filter identifier + * Returns unique filter identifier. * * @return string */ @@ -1446,9 +1623,8 @@ public function getId() return $this->id; } - /** - * Sets persistence + * Sets persistence. * * @param $persistence * @@ -1462,9 +1638,9 @@ public function setPersistence($persistence) } /** - * Returns persistence + * Returns persistence. * - * @return boolean + * @return bool */ public function getPersistence() { @@ -1484,33 +1660,33 @@ public function setDataJunction($dataJunction) } /** - * Sets Limits + * Sets Limits. * * @param mixed $limits e.g. 10, array(10, 1000) or array(10 => '10', 1000 => '1000') * - * @return self - * * @throws \InvalidArgumentException + * + * @return self */ public function setLimits($limits) { if (is_array($limits)) { - if ((int)key($limits) === 0) { + if ((int) key($limits) === 0) { $this->limits = array_combine($limits, $limits); } else { $this->limits = $limits; } } elseif (is_int($limits)) { - $this->limits = array($limits => (string)$limits); + $this->limits = [$limits => (string) $limits]; } else { - throw new \InvalidArgumentException('Limit has to be array or integer'); + throw new \InvalidArgumentException(self::NOT_VALID_LIMIT_EX_MSG); } return $this; } /** - * Returns limits + * Returns limits. * * @return array */ @@ -1520,7 +1696,7 @@ public function getLimits() } /** - * Returns selected Limit (Rows Per Page) + * Returns selected Limit (Rows Per Page). * * @return mixed */ @@ -1530,7 +1706,7 @@ public function getLimit() } /** - * Sets default Limit + * Sets default Limit. * * @param $limit * @@ -1544,7 +1720,7 @@ public function setDefaultLimit($limit) } /** - * Sets default Page + * Sets default Page. * * @param $page * @@ -1558,7 +1734,7 @@ public function setDefaultPage($page) } /** - * Sets default Tweak + * Sets default Tweak. * * @param $tweakId * @@ -1572,27 +1748,27 @@ public function setDefaultTweak($tweakId) } /** - * Sets current Page (internal) + * Sets current Page (internal). * * @param $page * - * @return self - * * @throws \InvalidArgumentException + * + * @return self */ public function setPage($page) { - if ((int)$page >= 0) { + if ((int) $page >= 0) { $this->page = (int) $page; } else { - throw new \InvalidArgumentException('Page must be a positive number'); + throw new \InvalidArgumentException(self::PAGE_NOT_VALID_EX_MSG); } return $this; } /** - * Returns current page + * Returns current page. * * @return int */ @@ -1601,9 +1777,8 @@ public function getPage() return $this->page; } - /** - * Returnd grid display data as rows - internal helper for templates + * Returnd grid display data as rows - internal helper for templates. * * @return mixed */ @@ -1613,17 +1788,23 @@ public function getRows() } /** - * Return count of available pages + * Return count of available pages. * * @return float */ public function getPageCount() { - return ceil($this->getTotalCount() / $this->getLimit()); + $pageCount = 1; + if ($this->getLimit() > 0) { + $pageCount = ceil($this->getTotalCount() / $this->getLimit()); + } + + // @todo why this should be a float? + return $pageCount; } /** - * Returns count of filtred rows(items) from source + * Returns count of filtred rows(items) from source. * * @return mixed */ @@ -1633,18 +1814,18 @@ public function getTotalCount() } /** - * Sets the max results of the grid + * Sets the max results of the grid. * * @param int $maxResults * - * @return self - * * @throws \InvalidArgumentException + * + * @return self */ public function setMaxResults($maxResults = null) { if ((is_int($maxResults) && $maxResults < 0) && $maxResults !== null) { - throw new \InvalidArgumentException('Max results must be a positive number.'); + throw new \InvalidArgumentException(self::NOT_VALID_MAX_RESULT_EX_MSG); } $this->maxResults = $maxResults; @@ -1653,9 +1834,9 @@ public function setMaxResults($maxResults = null) } /** - * Return true if the grid is filtered + * Return true if the grid is filtered. * - * @return boolean + * @return bool */ public function isFiltered() { @@ -1669,29 +1850,31 @@ public function isFiltered() } /** - * Return true if if title panel is visible in template - internal helper + * Return true if if title panel is visible in template - internal helper. * * @return bool */ public function isTitleSectionVisible() { - if ($this->showTitles == true) { + if ($this->showTitles === true) { foreach ($this->columns as $column) { if ($column->getTitle() != '') { return true; } } } + + return false; } /** - * Return true if filter panel is visible in template - internal helper + * Return true if filter panel is visible in template - internal helper. * * @return bool */ public function isFilterSectionVisible() { - if ($this->showFilters == true) { + if ($this->showFilters === true) { foreach ($this->columns as $column) { if ($column->isFilterable() && $column->getType() != 'massaction' && $column->getType() != 'actions') { return true; @@ -1703,18 +1886,24 @@ public function isFilterSectionVisible() } /** - * Return true if pager panel is visible in template - internal helper + * Return true if pager panel is visible in template - internal helper. * * @return bool return true if pager is visible */ public function isPagerSectionVisible() { + $limits = $this->getLimits(); + + if (empty($limits)) { + return false; + } + // true when totalCount rows exceed the minimum pager limit - return (min(array_keys($this->getLimits())) <= $this->totalCount); + return min(array_keys($limits)) < $this->totalCount; } /** - * Hides Filters Panel + * Hides Filters Panel. * * @return self */ @@ -1726,7 +1915,7 @@ public function hideFilters() } /** - * Hides Titles panel + * Hides Titles panel. * * @return self */ @@ -1738,9 +1927,9 @@ public function hideTitles() } /** - * Adds Column Extension - internal helper + * Adds Column Extension - internal helper. * - * @param Column\Column $extension + * @param Column $extension * * @return self */ @@ -1752,7 +1941,7 @@ public function addColumnExtension($extension) } /** - * Set a prefix title + * Set a prefix title. * * @param $prefixTitle string * @@ -1766,7 +1955,7 @@ public function setPrefixTitle($prefixTitle) } /** - * Get the prefix title + * Get the prefix title. * * @return string */ @@ -1776,7 +1965,7 @@ public function getPrefixTitle() } /** - * Set the no data message + * Set the no data message. * * @param $noDataMessage string * @@ -1790,7 +1979,7 @@ public function setNoDataMessage($noDataMessage) } /** - * Get the no data message + * Get the no data message. * * @return string */ @@ -1800,7 +1989,7 @@ public function getNoDataMessage() } /** - * Set the no result message + * Set the no result message. * * @param $noResultMessage string * @@ -1814,7 +2003,7 @@ public function setNoResultMessage($noResultMessage) } /** - * Get the no result message + * Get the no result message. * * @return string */ @@ -1824,7 +2013,7 @@ public function getNoResultMessage() } /** - * Sets a list of columns to hide when the grid is output + * Sets a list of columns to hide when the grid is output. * * @param array $columnIds * @@ -1839,7 +2028,7 @@ public function setHiddenColumns($columnIds) /** * Sets a list of columns to show when the grid is output - * It acts as a mask; Other columns will be set as hidden + * It acts as a mask; Other columns will be set as hidden. * * @param array $columnIds * @@ -1853,7 +2042,7 @@ public function setVisibleColumns($columnIds) } /** - * Sets on the visibility of columns + * Sets on the visibility of columns. * * @param string|array $columnIds * @@ -1869,7 +2058,7 @@ public function showColumns($columnIds) } /** - * Sets off the visiblilty of columns + * Sets off the visiblilty of columns. * * @param string|array $columnIds * @@ -1885,9 +2074,9 @@ public function hideColumns($columnIds) } /** - * Sets the size of the default action column + * Sets the size of the default action column. * - * @param integer $size + * @param int $size * * @return self */ @@ -1899,7 +2088,7 @@ public function setActionsColumnSize($size) } /** - * Sets the title of the default action column + * Sets the title of the default action column. * * @param string $title * @@ -1913,17 +2102,17 @@ public function setActionsColumnTitle($title) } /** - * Default delete action + * Default delete action. * - * @param $ids + * @param array $ids */ - public function deleteAction($ids, $actionAllKeys) + public function deleteAction(array $ids) { - $this->source->delete($ids, $actionAllKeys); + $this->source->delete($ids); } /** - * Get a clone of the grid + * Get a clone of the grid. */ public function __clone() { @@ -1934,13 +2123,13 @@ public function __clone() /****** HELPER ******/ /** - * Redirects or Renders a view - helper function + * Redirects or Renders a view - helper function. * - * @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 + * @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 + * @return Response|array A Response instance */ public function getGridResponse($param1 = null, $param2 = null, Response $response = null) { @@ -1965,21 +2154,29 @@ public function getGridResponse($param1 = null, $param2 = null, Response $respon $view = $param1; } - $parameters = array_merge(array('grid' => $this), $parameters); + $parameters = array_merge(['grid' => $this], $parameters); if ($view === null) { return $parameters; } else { - return $this->container->get('templating')->renderResponse($view, $parameters, $response); + $content = $this->twig->render($view, $parameters); + + if (null === $response) { + $response = new Response(); + } + + $response->setContent($content); + + return $response; } } } /** - * Extract raw data of columns + * Extract raw data of columns. * - * @param string|array $columnNames The name of the extract columns. If null, all the columns are return. - * @param boolean $namedIndexes If sets to true, named indexes will be used + * @param string|array $columnNames The name of the extract columns. If null, all the columns are return. + * @param bool $namedIndexes If sets to true, named indexes will be used * * @return array Raw data of columns */ @@ -1992,9 +2189,9 @@ public function getRawData($columnNames = null, $namedIndexes = true) } $columnNames = (array) $columnNames; - $result = array(); + $result = []; foreach ($this->rows as $row) { - $resultRow = array(); + $resultRow = []; foreach ($columnNames as $columnName) { if ($namedIndexes) { $resultRow[$columnName] = $row->getField($columnName); @@ -2010,22 +2207,23 @@ public function getRawData($columnNames = null, $namedIndexes = true) } /** - * Returns an array of the active filters of the grid stored in session + * Returns an array of the active filters of the grid stored in session. * - * @return Filter[] * @throws \Exception + * + * @return Filter[] */ public function getFilters() { if ($this->hash === null) { - throw new \Exception('getFilters method is only available in the manipulate callback function or after the call of the method isRedirected of the grid.'); + throw new \Exception(self::GET_FILTERS_NO_REQUEST_HANDLED_EX_MSG); } if ($this->sessionFilters === null) { - $this->sessionFilters = array(); + $this->sessionFilters = []; $session = $this->sessionData; - $requestQueries = array( + $requestQueries = [ self::REQUEST_QUERY_MASS_ACTION_ALL_KEYS_SELECTED, self::REQUEST_QUERY_MASS_ACTION, self::REQUEST_QUERY_EXPORT, @@ -2035,7 +2233,7 @@ public function getFilters() self::REQUEST_QUERY_TEMPLATE, self::REQUEST_QUERY_RESET, MassActionColumn::ID, - ); + ]; foreach ($requestQueries as $request_query) { unset($session[$request_query]); @@ -2049,7 +2247,7 @@ public function getFilters() $operator = $this->getColumn($columnId)->getDefaultOperator(); } - if (! isset($sessionFilter['to'])) { + if (!isset($sessionFilter['to']) && isset($sessionFilter['from'])) { $sessionFilter = $sessionFilter['from']; } @@ -2061,17 +2259,19 @@ public function getFilters() } /** - * Returns the filter of a column stored in session + * Returns the filter of a column stored in session. * * @param string $columnId - * Id of the column - * @return Filter + * Id of the column + * * @throws \Exception + * + * @return Filter */ public function getFilter($columnId) { if ($this->hash === null) { - throw new \Exception('getFilters method is only available in the manipulate callback function or after the call of the method isRedirected of the grid.'); + throw new \Exception(self::GET_FILTERS_NO_REQUEST_HANDLED_EX_MSG); } $sessionFilters = $this->getFilters(); @@ -2083,16 +2283,146 @@ public function getFilter($columnId) * A filter of the column is stored in session ? * * @param string $columnId - * Id of the column - * @return boolean + * Id of the column + * * @throws \Exception + * + * @return bool */ public function hasFilter($columnId) { if ($this->hash === null) { - throw new \Exception('hasFilters method is only available in the manipulate callback function or after the call of the method isRedirected of the grid.'); + throw new \Exception(self::HAS_FILTER_NO_REQUEST_HANDLED_EX_MSG); } - return getFilter($columnId) !== null; + return $this->getFilter($columnId) !== null; + } + + /** + * Get default order (e.g. my_column_id|asc). + * + * @return string + */ + public function getDefaultOrder() + { + return $this->defaultOrder; + } + + /** + * Get the value of maxResults + * + * @return int + */ + public function getMaxResults() + { + return $this->maxResults; + } + + /** + * Get the value of lazyAddColumn + */ + public function getLazyAddColumn() + { + return $this->lazyAddColumn; + } + + /** + * Get default Tweak. + * + * @return string + */ + public function getDefaultTweak() + { + return $this->defaultTweak; + } + + /** + * Get the value of lazyVisibleColumns + */ + public function getLazyVisibleColumns() + { + return $this->lazyVisibleColumns; + } + + /** + * Get the value of lazyHideShowColumns + */ + public function getLazyHideShowColumns() + { + return $this->lazyHideShowColumns; + } + + /** + * Get the value of actionsColumnSize + */ + public function getActionsColumnSize() + { + return $this->actionsColumnSize; + } + + /** + * Get the value of actionsColumnTitle + */ + public function getActionsColumnTitle() + { + return $this->actionsColumnTitle; + } + + /** + * Get the value of showFilters + * + * @return bool + */ + public function getShowFilters() + { + return $this->showFilters; + } + + /** + * Get the value of showTitles + * + * @return bool + */ + public function getShowTitles() + { + return $this->showTitles; + } + + /** + * Get the value of lazyHiddenColumns + */ + public function getLazyHiddenColumns() + { + return $this->lazyHiddenColumns; + } + + /** + * Get the value of newSession + * + * @return bool + */ + public function getNewSession() + { + return $this->newSession; + } + + /** + * Get default filters. + * + * @return array + */ + public function getDefaultFilters() + { + return $this->defaultFilters; + } + + /** + * Get permanent filters. + * + * @return array + */ + public function getPermanentFilters() + { + return $this->permanentFilters; } } diff --git a/Grid/GridBuilder.php b/Grid/GridBuilder.php new file mode 100644 index 00000000..603ad5df --- /dev/null +++ b/Grid/GridBuilder.php @@ -0,0 +1,133 @@ +container = $container; + $this->factory = $factory; + $this->securityContext = $securityContext; + $this->twig = $twig; + } + + /** + * {@inheritdoc} + */ + public function add($name, $type, array $options = []) + { + if (!$type instanceof Column) { + if (!is_string($type)) { + throw new UnexpectedTypeException($type, 'string, APY\DataGridBundle\Grid\Column\Column'); + } + + $type = $this->factory->createColumn($name, $type, $options); + } + + $this->columns[$name] = $type; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function get($name) + { + if (!$this->has($name)) { + throw new InvalidArgumentException(sprintf('The column with the name "%s" does not exist.', $name)); + } + + $column = $this->columns[$name]; + + return $column; + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return isset($this->columns[$name]); + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + unset($this->columns[$name]); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getGrid() + { + $config = $this->getGridConfig(); + + $grid = new Grid($this->container, $this->securityContext, $this->twig, $config->getName(), $config); + + foreach ($this->columns as $column) { + $grid->addColumn($column); + } + + if (!empty($this->actions)) { + foreach ($this->actions as $columnId => $actions) { + foreach ($actions as $action) { + $grid->addRowAction($action); + } + } + } + + $grid->initialize(); + + return $grid; + } +} diff --git a/Grid/GridBuilderInterface.php b/Grid/GridBuilderInterface.php new file mode 100644 index 00000000..25a3dbec --- /dev/null +++ b/Grid/GridBuilderInterface.php @@ -0,0 +1,58 @@ +name = $name; + $this->options = $options; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getSource() + { + return $this->source; + } + + /** + * Set Source. + * + * @param Source $source + * + * @return $this + */ + public function setSource(Source $source) + { + $this->source = $source; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getType() + { + return $this->type; + } + + /** + * Set Type. + * + * @param GridTypeInterface $type + * + * @return $this + */ + public function setType(GridTypeInterface $type) + { + $this->type = $type; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getRoute() + { + return $this->route; + } + + /** + * Set Route. + * + * @param mixed $route + * + * @return $this + */ + public function setRoute($route) + { + $this->route = $route; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getRouteParameters() + { + return $this->routeParameters; + } + + /** + * Set RouteParameters. + * + * @param array $routeParameters + * + * @return $this + */ + public function setRouteParameters(array $routeParameters) + { + $this->routeParameters = $routeParameters; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isPersisted() + { + return $this->persistence; + } + + /** + * Set Persistence. + * + * @param mixed $persistence + * + * @return $this + */ + public function setPersistence($persistence) + { + $this->persistence = $persistence; + + return $this; + } + + + /** + * {@inheritdoc} + */ + public function getPersistence() + { + return $this->persistence; + } + + /** + * {@inheritdoc} + */ + public function getPage() + { + return $this->page; + } + + /** + * Set Page. + * + * @param int $page + * + * @return $this + */ + public function setPage($page) + { + $this->page = $page; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return $this->options; + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return array_key_exists($name, $this->options); + } + + /** + * {@inheritdoc} + */ + public function getOption($name, $default = null) + { + return array_key_exists($name, $this->options) ? $this->options[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function getMaxPerPage() + { + return $this->limit; + } + + /** + * Set Limit. + * + * @param int $limit + * + * @return $this + */ + public function setMaxPerPage($limit) + { + $this->limit = $limit; + + return $this; + } + + /** + * Get MaxResults. + * + * @return int + */ + public function getMaxResults() + { + return $this->maxResults; + } + + /** + * Set MaxResults. + * + * @param int $maxResults + * + * @return $this + */ + public function setMaxResults($maxResults) + { + $this->maxResults = $maxResults; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isSortable() + { + return $this->sortable; + } + + /** + * Set Sortable. + * + * @param bool $sortable + * + * @return $this + */ + public function setSortable($sortable) + { + $this->sortable = $sortable; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isFilterable() + { + return $this->filterable; + } + + /** + * Set Filterable. + * + * @param bool $filterable + * + * @return $this + */ + public function setFilterable($filterable) + { + $this->filterable = $filterable; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOrder() + { + return $this->order; + } + + /** + * Set Order. + * + * @param string $order + * + * @return $this + */ + public function setOrder($order) + { + $this->order = $order; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getSortBy() + { + return $this->sortBy; + } + + /** + * Set SortBy. + * + * @param string $sortBy + * + * @return $this + */ + public function setSortBy($sortBy) + { + $this->sortBy = $sortBy; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getGroupBy() + { + return $this->groupBy; + } + + /** + * Set GroupBy. + * + * @param array|string $groupBy + * + * @return $this + */ + public function setGroupBy($groupBy) + { + $this->groupBy = $groupBy; + + return $this; + } + + /** + * @param RowActionInterface $action + * + * @return $this + */ + public function addAction(RowActionInterface $action) + { + $this->actions[$action->getColumn()][] = $action; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getActions() + { + return $this->actions; + } + + /** + * {@inheritdoc} + */ + public function getGridConfig() + { + $config = clone $this; + + return $config; + } +} diff --git a/Grid/GridConfigBuilderInterface.php b/Grid/GridConfigBuilderInterface.php new file mode 100644 index 00000000..39e14d8a --- /dev/null +++ b/Grid/GridConfigBuilderInterface.php @@ -0,0 +1,18 @@ +container = $container; + $this->registry = $registry; + $this->securityContext = $securityContext; + $this->twig = $twig; + } + + /** + * {@inheritdoc} + */ + public function create($type = null, Source $source = null, array $options = []) + { + return $this->createBuilder($type, $source, $options)->getGrid(); + } + + /** + * {@inheritdoc} + */ + public function createBuilder($type = 'grid', Source $source = null, array $options = []) + { + $type = $this->resolveType($type); + $options = $this->resolveOptions($type, $source, $options); + + $builder = new GridBuilder($this->container, $this->securityContext, $this->twig, $this, $type->getName(), $options); + $builder->setType($type); + + $type->buildGrid($builder, $options); + + return $builder; + } + + /** + * {@inheritdoc} + */ + public function createColumn($name, $type, array $options = []) + { + if (!$type instanceof Column) { + if (!is_string($type)) { + throw new UnexpectedTypeException($type, 'string, APY\DataGridBundle\Grid\Column\Column'); + } + + $column = clone $this->registry->getColumn($type); + + $column->__initialize(array_merge([ + 'id' => $name, + 'title' => $name, + 'field' => $name, + 'source' => true, + ], $options)); + } else { + $column = $type; + $column->setId($name); + } + + return $column; + } + + /** + * Returns an instance of type. + * + * @param string|GridTypeInterface $type The type of the grid + * + * @return GridTypeInterface + */ + private function resolveType($type) + { + if (!$type instanceof GridTypeInterface) { + if (!is_string($type)) { + throw new UnexpectedTypeException($type, 'string, APY\DataGridBundle\Grid\GridTypeInterface'); + } + + $type = $this->registry->getType($type); + } + + return $type; + } + + /** + * Returns the options resolved. + * + * @param GridTypeInterface $type + * @param Source $source + * @param array $options + * + * @return array + */ + private function resolveOptions(GridTypeInterface $type, Source $source = null, array $options = []): array + { + $resolver = new OptionsResolver(); + + $type->configureOptions($resolver); + + if (null !== $source && !isset($options['source'])) { + $options['source'] = $source; + } + + $options = $resolver->resolve($options); + + return $options; + } +} diff --git a/Grid/GridFactoryInterface.php b/Grid/GridFactoryInterface.php new file mode 100644 index 00000000..29fba47b --- /dev/null +++ b/Grid/GridFactoryInterface.php @@ -0,0 +1,47 @@ +container = $container; - $this->grids = new \SplObjectStorage(); + $this->twig= $twig; + $this->grids = new SplObjectStorage(); } - public function getIterator() + public function getIterator(): \Traversable { return $this->grids; } - public function count() + public function count(): int { return $this->grids->count(); } /** * @param mixed $id + * * @return Grid */ - public function createGrid($id = null) + public function createGrid($id = null): Grid { $grid = $this->container->get('grid'); @@ -60,13 +75,13 @@ public function createGrid($id = null) return $grid; } - public function isReadyForRedirect() + public function isReadyForRedirect(): bool { if ($this->grids->count() == 0) { - throw new \RuntimeException('No grid has been added to the manager.'); + throw new RuntimeException(self::NO_GRID_EX_MSG); } - $checkHash = array(); + $checkHash = []; $isReadyForRedirect = false; $this->grids->rewind(); @@ -85,7 +100,7 @@ public function isReadyForRedirect() } if (in_array($grid->getHash(), $checkHash)) { - throw new \RuntimeException('Some grids seem similar. Please set an Indentifier for your grids.'); + throw new RuntimeException(self::SAME_GRID_HASH_EX_MSG); } $checkHash[] = $grid->getHash(); @@ -96,20 +111,20 @@ public function isReadyForRedirect() return $isReadyForRedirect; } - public function isReadyForExport() + public function isReadyForExport(): bool { if ($this->grids->count() == 0) { - throw new \RuntimeException('No grid has been added to the manager.'); + throw new RuntimeException(self::NO_GRID_EX_MSG); } - $checkHash = array(); + $checkHash = []; $this->grids->rewind(); while ($this->grids->valid()) { $grid = $this->grids->current(); if (in_array($grid->getHash(), $checkHash)) { - throw new \RuntimeException('Some grids seem similar. Please set an Indentifier for your grids.'); + throw new RuntimeException(self::SAME_GRID_HASH_EX_MSG); } $checkHash[] = $grid->getHash(); @@ -126,7 +141,7 @@ public function isReadyForExport() return false; } - public function isMassActionRedirect() + public function isMassActionRedirect(): bool { $this->grids->rewind(); while ($this->grids->valid()) { @@ -147,11 +162,11 @@ public function isMassActionRedirect() /** * Renders a view. * - * @param string|array $param1 The view name or an array of parameters to pass to the view - * @param string|array $param1 The view name or an array of parameters to pass to the view - * @param Response $response A response instance + * @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|null $response A response instance * - * @return Response A Response instance + * @return Response|array A Response instance */ public function getGridManagerResponse($param1 = null, $param2 = null, Response $response = null) { @@ -179,16 +194,24 @@ public function getGridManagerResponse($param1 = null, $param2 = null, Response $i = 1; $this->grids->rewind(); while ($this->grids->valid()) { - $parameters = array_merge(array('grid'.$i => $this->grids->current()), $parameters); + $parameters = array_merge(['grid' . $i => $this->grids->current()], $parameters); $this->grids->next(); - $i++; + ++$i; } if ($view === null) { return $parameters; } - return $this->container->get('templating')->renderResponse($view, $parameters, $response); + $content = $this->twig->render($view, $parameters); + + if (null === $response) { + $response = new Response(); + } + + $response->setContent($content); + + return $response; } } @@ -201,4 +224,28 @@ public function setRouteUrl($routeUrl) { $this->routeUrl = $routeUrl; } + + /** + * Get the value of grids + */ + public function getGrids() + { + return $this->grids; + } + + /** + * Get the value of exportGrid + */ + public function getExportGrid() + { + return $this->exportGrid; + } + + /** + * Get the value of massActionGrid + */ + public function getMassActionGrid() + { + return $this->massActionGrid; + } } diff --git a/Grid/GridRegistry.php b/Grid/GridRegistry.php new file mode 100644 index 00000000..b39f69af --- /dev/null +++ b/Grid/GridRegistry.php @@ -0,0 +1,123 @@ +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; + } +} diff --git a/Grid/GridRegistryInterface.php b/Grid/GridRegistryInterface.php new file mode 100644 index 00000000..750259ed --- /dev/null +++ b/Grid/GridRegistryInterface.php @@ -0,0 +1,51 @@ +showOnlySourceColumns = $showOnlySourceColumns; } - public function accept() + public function accept(): bool { $current = $this->getInnerIterator()->current(); diff --git a/Grid/Helper/ORMCountWalker.php b/Grid/Helper/ORMCountWalker.php index 32b45832..9bfc3a99 100644 --- a/Grid/Helper/ORMCountWalker.php +++ b/Grid/Helper/ORMCountWalker.php @@ -1,6 +1,7 @@ * @copyright Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/) * @license http://hobodave.com/license.txt New BSD License @@ -40,21 +41,19 @@ class ORMCountWalker extends TreeWalkerAdapter * * @param SelectStatement $AST * - * @return void - * * @throws \RuntimeException */ public function walkSelectStatement(SelectStatement $AST) { - $rootComponents = array(); - foreach ($this->_getQueryComponents() AS $dqlAlias => $qComp) { + $rootComponents = []; + foreach ($this->_getQueryComponents() as $dqlAlias => $qComp) { if (array_key_exists('parent', $qComp) && $qComp['parent'] === null && $qComp['nestingLevel'] == 0) { - $rootComponents[] = array($dqlAlias => $qComp); + $rootComponents[] = [$dqlAlias => $qComp]; } } if (count($rootComponents) > 1) { - throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction"); + throw new \RuntimeException('Cannot count query which selects two FROM components, cannot make distinction'); } $root = reset($rootComponents); @@ -67,10 +66,9 @@ public function walkSelectStatement(SelectStatement $AST) ); $pathExpression->type = PathExpression::TYPE_STATE_FIELD; - // Remove the variables which are not used by other clauses foreach ($AST->selectClause->selectExpressions as $key => $selectExpression) { - if ($selectExpression->fieldIdentificationVariable == null) { + if ($selectExpression->fieldIdentificationVariable === null) { unset($AST->selectClause->selectExpressions[$key]); } elseif ($selectExpression->expression instanceof PathExpression) { $groupByClause[] = $selectExpression->expression; diff --git a/Grid/Mapping/Column.php b/Grid/Mapping/Column.php index a805f3f2..64203434 100644 --- a/Grid/Mapping/Column.php +++ b/Grid/Mapping/Column.php @@ -23,7 +23,7 @@ class Column public function __construct($metadata) { $this->metadata = $metadata; - $this->groups = isset($metadata['groups']) ? (array) $metadata['groups'] : array('default'); + $this->groups = isset($metadata['groups']) ? (array) $metadata['groups'] : ['default']; } public function getMetadata() diff --git a/Grid/Mapping/Driver/Annotation.php b/Grid/Mapping/Driver/Annotation.php index 61463936..a414e09e 100644 --- a/Grid/Mapping/Driver/Annotation.php +++ b/Grid/Mapping/Driver/Annotation.php @@ -29,7 +29,7 @@ class Annotation implements DriverInterface public function __construct($reader) { $this->reader = $reader; - $this->columns = $this->fields = $this->loaded = $this->groupBy = $this->filterable = $this->sortable = array(); + $this->columns = $this->fields = $this->loaded = $this->groupBy = $this->filterable = $this->sortable = []; } public function getClassColumns($class, $group = 'default') @@ -48,14 +48,16 @@ public function getFieldsMetadata($class, $group = 'default') public function getGroupBy($class, $group = 'default') { - return isset($this->groupBy[$class][$group]) ? $this->groupBy[$class][$group] : array(); + return isset($this->groupBy[$class][$group]) ? $this->groupBy[$class][$group] : []; } protected function loadMetadataFromReader($className, $group = 'default') { - if (isset($this->loaded[$className][$group])) return; + if (isset($this->loaded[$className][$group])) { + return; + } - $reflectionCollection = array(); + $reflectionCollection = []; $reflectionCollection[] = $reflection = new \ReflectionClass($className); while (false !== $reflection = $reflection->getParentClass()) { @@ -70,7 +72,7 @@ protected function loadMetadataFromReader($className, $group = 'default') } foreach ($reflection->getProperties() as $property) { - $this->fields[$className][$group][$property->getName()] = array(); + $this->fields[$className][$group][$property->getName()] = []; foreach ($this->reader->getPropertyAnnotations($property) as $class) { $this->getMetadataFromClassProperty($className, $class, $property->getName(), $group); @@ -109,7 +111,7 @@ protected function getMetadataFromClassProperty($className, $class, $name = null if ($name === null) { // Class Column annotation if (isset($metadata['id'])) { $metadata['source'] = false; - $this->fields[$className][$group][$metadata['id']] = array(); + $this->fields[$className][$group][$metadata['id']] = []; } else { throw new \Exception(sprintf('Missing parameter `id` in annotations for extra column of class %s', $className)); } @@ -129,7 +131,7 @@ protected function getMetadataFromClassProperty($className, $class, $name = null // Check the group of the annotation and don't override if an annotation with the group have already been defined if (isset($metadata['groups']) && !in_array($group, (array) $metadata['groups']) - || isset($this->fields[$className][$group][$metadata['id']]['groups'])) { + || isset($this->fields[$className][$group][$metadata['id']]['groups'])) { return; } diff --git a/Grid/Mapping/Metadata/DriverHeap.php b/Grid/Mapping/Metadata/DriverHeap.php index 280fe6cf..10d37641 100644 --- a/Grid/Mapping/Metadata/DriverHeap.php +++ b/Grid/Mapping/Metadata/DriverHeap.php @@ -17,15 +17,16 @@ class DriverHeap extends \SplPriorityQueue { /** - * (non-PHPdoc) + * (non-PHPdoc). + * * @see SplPriorityQueue::compare() */ - public function compare($priority1, $priority2) - { - if ($priority1 === $priority2) { - return 0; - } + public function compare($priority1, $priority2): int + { + if ($priority1 === $priority2) { + return 0; + } - return $priority1 > $priority2 ? -1 : 1; - } + return $priority1 > $priority2 ? -1 : 1; + } } diff --git a/Grid/Mapping/Metadata/Manager.php b/Grid/Mapping/Metadata/Manager.php index dd908147..491127a0 100644 --- a/Grid/Mapping/Metadata/Manager.php +++ b/Grid/Mapping/Metadata/Manager.php @@ -33,6 +33,7 @@ public function addDriver($driver, $priority) /** * @todo remove this hack + * * @return \APY\DataGridBundle\Grid\Mapping\Metadata\DriverHeap */ public function getDrivers() @@ -44,7 +45,7 @@ public function getMetadata($className, $group = 'default') { $metadata = new Metadata(); - $columns = $fieldsMetadata = $groupBy = array(); + $columns = $fieldsMetadata = $groupBy = []; foreach ($this->getDrivers() as $driver) { $columns = array_merge($columns, $driver->getClassColumns($className, $group)); @@ -52,12 +53,12 @@ public function getMetadata($className, $group = 'default') $groupBy = array_merge($groupBy, $driver->getGroupBy($className, $group)); } - $mappings = $cols = array(); + $mappings = $cols = []; foreach ($columns as $fieldName) { - $map = array(); + $map = []; - foreach($fieldsMetadata as $field) { + foreach ($fieldsMetadata as $field) { if (isset($field[$fieldName]) && (!isset($field[$fieldName]['groups']) || in_array($group, (array) $field[$fieldName]['groups']))) { $map = array_merge($map, $field[$fieldName]); } diff --git a/Grid/Mapping/Metadata/Metadata.php b/Grid/Mapping/Metadata/Metadata.php index a48280f2..37373815 100644 --- a/Grid/Mapping/Metadata/Metadata.php +++ b/Grid/Mapping/Metadata/Metadata.php @@ -77,9 +77,12 @@ public function getName() /** * @todo move to another place + * * @param $columnExtensions - * @return \SplObjectStorage + * * @throws \Exception + * + * @return \SplObjectStorage */ public function getColumnsFromMapping($columnExtensions) { @@ -89,16 +92,24 @@ public function getColumnsFromMapping($columnExtensions) $params = $this->getFieldMapping($value); $type = $this->getFieldMappingType($value); - /** todo move available extensions from columns */ + /* todo move available extensions from columns */ if ($columnExtensions->hasExtensionForColumnType($type)) { $column = clone $columnExtensions->getExtensionForColumnType($type); $column->__initialize($params); $columns->attach($column); } else { - throw new \Exception(sprintf("No suitable Column Extension found for column type: %s", $type)); + throw new \Exception(sprintf('No suitable Column Extension found for column type: %s', $type)); } } return $columns; } + + /** + * Get the value of fieldsMappings + */ + public function getFieldsMappings() + { + return $this->fieldsMappings; + } } diff --git a/Grid/Mapping/Source.php b/Grid/Mapping/Source.php index 49540130..e5ff5b30 100644 --- a/Grid/Mapping/Source.php +++ b/Grid/Mapping/Source.php @@ -23,13 +23,13 @@ class Source protected $groups; protected $groupBy; - public function __construct($metadata = array()) + public function __construct($metadata = []) { - $this->columns = (isset($metadata['columns']) && $metadata['columns'] != '') ? array_map('trim', explode(',', $metadata['columns'])) : array(); + $this->columns = (isset($metadata['columns']) && $metadata['columns'] != '') ? array_map('trim', explode(',', $metadata['columns'])) : []; $this->filterable = isset($metadata['filterable']) ? $metadata['filterable'] : true; $this->sortable = isset($metadata['sortable']) ? $metadata['sortable'] : true; - $this->groups = (isset($metadata['groups']) && $metadata['groups'] != '') ? (array) $metadata['groups'] : array('default'); - $this->groupBy = (isset($metadata['groupBy']) && $metadata['groupBy'] != '') ? (array) $metadata['groupBy'] : array(); + $this->groups = (isset($metadata['groups']) && $metadata['groups'] != '') ? (array) $metadata['groups'] : ['default']; + $this->groupBy = (isset($metadata['groupBy']) && $metadata['groupBy'] != '') ? (array) $metadata['groupBy'] : []; } public function getColumns() @@ -61,4 +61,12 @@ public function getGroupBy() { return $this->groupBy; } + + /** + * Get the value of sortable + */ + public function getSortable() + { + return $this->sortable; + } } diff --git a/Grid/Row.php b/Grid/Row.php index 6f6263cb..00a2d855 100644 --- a/Grid/Row.php +++ b/Grid/Row.php @@ -12,27 +12,56 @@ namespace APY\DataGridBundle\Grid; +use Doctrine\ORM\EntityRepository; + class Row { + /** @var array */ protected $fields; + + /** @var string */ protected $class; + + /** @var string */ protected $color; + + /** @var string|null */ protected $legend; + + /** @var mixed */ protected $primaryField; + + /** @var mixed */ protected $entity; + + /** @var EntityRepository */ protected $repository; public function __construct() { - $this->fields = array(); + $this->fields = []; $this->color = ''; } - public function setRepository($repository) + /** + * @param EntityRepository $repository + */ + public function setRepository(EntityRepository $repository) { $this->repository = $repository; } + /** + * @return EntityRepository + */ + public function getRepository() + { + return $this->repository; + } + + /** + * @return null|object + */ public function getEntity() { $primaryKeyValue = current($this->getPrimaryKeyValue()); @@ -40,87 +69,151 @@ public function getEntity() return $this->repository->find($primaryKeyValue); } - public function setField($rowId, $value) + /** + * @return array + */ + public function getPrimaryKeyValue() { - $this->fields[$rowId] = $value; + $primaryFieldValue = $this->getPrimaryFieldValue(); - return $this; + if (is_array($primaryFieldValue)) { + return $primaryFieldValue; + } + + // @todo: is that correct? shouldn't be [$this->primaryField => $primaryFieldValue] ?? + return ['id' => $primaryFieldValue]; } - public function getField($rowId) + /** + * @throws \InvalidArgumentException + * + * @return array|mixed + */ + public function getPrimaryFieldValue() { - return isset($this->fields[$rowId]) ? $this->fields[$rowId] : ''; + if (null === $this->primaryField) { + throw new \InvalidArgumentException('Primary column must be defined'); + } + + if (is_array($this->primaryField)) { + return array_intersect_key($this->fields, array_flip($this->primaryField)); + } + + if (!isset($this->fields[$this->primaryField])) { + throw new \InvalidArgumentException('Primary field not added to fields'); + } + + return $this->fields[$this->primaryField]; } - public function setClass($class) + /** + * @param mixed $primaryField + * + * @return $this + */ + public function setPrimaryField($primaryField) { - $this->class = $class; + $this->primaryField = $primaryField; return $this; } - public function getClass() + /** + * @return mixed + */ + public function getPrimaryField() { - return $this->class; + return $this->primaryField; } - public function setColor($color) + /** + * @param mixed $columnId + * @param mixed $value + * + * @return $this + */ + public function setField($columnId, $value) { - $this->color = $color; + $this->fields[$columnId] = $value; return $this; } - public function getColor() + /** + * @param mixed $columnId + * + * @return mixed + */ + public function getField($columnId) { - return $this->color; + return isset($this->fields[$columnId]) ? $this->fields[$columnId] : ''; } - public function setLegend($legend) + /** + * @return array + */ + public function getFields() { - $this->legend = $legend; + return $this->fields; + } + + /** + * @param string $class + * + * @return $this + */ + public function setClass($class) + { + $this->class = $class; return $this; } - public function getLegend() + /** + * @return string + */ + public function getClass() { - return $this->legend; + return $this->class; } - public function setPrimaryField($primaryField) + /** + * @param string $color + * + * @return $this + */ + public function setColor($color) { - $this->primaryField = $primaryField; + $this->color = $color; return $this; } - public function getPrimaryField() + /** + * @return string + */ + public function getColor() { - return $this->primaryField; + return $this->color; } - public function getPrimaryFieldValue() + /** + * @param string $legend + * + * @return $this + */ + public function setLegend($legend) { - if (null === $this->primaryField) { - throw new \InvalidArgumentException('Primary column must be defined'); - } - - if (is_array($this->primaryField)) { - return array_intersect_key($this->fields, array_flip($this->primaryField)); - } + $this->legend = $legend; - return $this->fields[$this->primaryField]; + return $this; } - public function getPrimaryKeyValue() + /** + * @return string|null + */ + public function getLegend() { - $primaryField = $this->getPrimaryFieldValue(); - - if (is_array($primaryField)) { - return $primaryField; - } - - return array('id' => $primaryField); + return $this->legend; } } diff --git a/Grid/Rows.php b/Grid/Rows.php index d2039f9d..fbc88dcf 100644 --- a/Grid/Rows.php +++ b/Grid/Rows.php @@ -14,12 +14,13 @@ class Rows implements \IteratorAggregate, \Countable { - /** - * @var \SplObjectStorage $rows - */ + /** @var \SplObjectStorage */ protected $rows; - public function __construct(array $rows = array()) + /** + * @param array $rows + */ + public function __construct(array $rows = []) { $this->rows = new \SplObjectStorage(); @@ -29,18 +30,20 @@ public function __construct(array $rows = array()) } /** - * (non-PHPdoc) + * (non-PHPdoc). + * * @see IteratorAggregate::getIterator() */ - public function getIterator() + public function getIterator(): \Traversable { return $this->rows; } /** - * Add row + * Add row. * * @param Row $row + * * @return Rows */ public function addRow(Row $row) @@ -51,16 +54,17 @@ public function addRow(Row $row) } /** - * (non-PHPdoc) + * (non-PHPdoc). + * * @see Countable::count() */ - public function count() + public function count(): int { return $this->rows->count(); } /** - * Returns the iterator as an array + * Returns the iterator as an array. * * @return array */ diff --git a/Grid/Source/Document.php b/Grid/Source/Document.php index bc6ed134..c14d4825 100644 --- a/Grid/Source/Document.php +++ b/Grid/Source/Document.php @@ -14,9 +14,12 @@ namespace APY\DataGridBundle\Grid\Source; -use APY\DataGridBundle\Grid\Rows; -use APY\DataGridBundle\Grid\Row; use APY\DataGridBundle\Grid\Column\Column; +use APY\DataGridBundle\Grid\Helper\ColumnsIterator; +use APY\DataGridBundle\Grid\Row; +use APY\DataGridBundle\Grid\Rows; +use Doctrine\ODM\MongoDB\Query\Builder as QueryBuilder; +use MongoDB\BSON\Regex; class Document extends Source { @@ -31,7 +34,7 @@ class Document extends Source protected $manager; /** - * e.g. Base\Cms\Document\Page + * e.g. Base\Cms\Document\Page. */ protected $class; @@ -41,7 +44,7 @@ class Document extends Source protected $odmMetadata; /** - * e.g. Cms:Page + * e.g. Cms:Page. */ protected $documentName; @@ -60,6 +63,16 @@ class Document extends Source */ protected $group; + /** + * @var array + */ + protected $referencedColumns = []; + + /** + * @var array + */ + protected $referencedMappings = []; + /** * @param string $documentName e.g. "Cms:Page" */ @@ -73,7 +86,7 @@ public function initialise($container) { $this->manager = $container->get('doctrine.odm.mongodb.document_manager'); $this->odmMetadata = $this->manager->getClassMetadata($this->documentName); - $this->class = $this->odmMetadata->getReflectionClass()->name; + $this->class = $this->odmMetadata->getReflectionClass()->getName(); $mapping = $container->get('grid.mapping.manager'); $mapping->addDriver($this, -1); @@ -82,7 +95,6 @@ public function initialise($container) /** * @param \APY\DataGridBundle\Grid\Columns $columns - * @return null */ public function getColumns($columns) { @@ -117,26 +129,22 @@ protected function normalizeOperator($operator) protected function normalizeValue($operator, $value) { switch ($operator) { - case Column::OPERATOR_EQ: - return new \MongoRegex('/^'.$value.'$/i'); case Column::OPERATOR_NEQ: - return new \MongoRegex('/^(?!'.$value.'$).*$/i'); + return new Regex('^(?!' . $value . '$).*$', 'i'); case Column::OPERATOR_LIKE: - return new \MongoRegex('/'.$value.'/i'); + return new Regex($value, 'i'); case Column::OPERATOR_NLIKE: - return new \MongoRegex('/^((?!'.$value.').)*$/i'); + return new Regex('^((?!' . $value . ').)*$', 'i'); case Column::OPERATOR_RLIKE: - return new \MongoRegex('/^'.$value.'/i'); + return new Regex('^' . $value, 'i'); case Column::OPERATOR_LLIKE: - return new \MongoRegex('/'.$value.'$/i'); - case Column::OPERATOR_SLIKE: - return new \MongoRegex('/'.$value.'/'); + return new Regex($value . '$', 'i'); case Column::OPERATOR_SLIKE: - return new \MongoRegex('/^((?!'.$value.').)*$/'); + return new Regex($value, ''); case Column::OPERATOR_RSLIKE: - return new \MongoRegex('/^'.$value.'/'); + return new Regex('^' . $value, ''); case Column::OPERATOR_LSLIKE: - return new \MongoRegex('/'.$value.'$/'); + return new Regex($value . '$', ''); case Column::OPERATOR_ISNULL: return false; case Column::OPERATOR_ISNOTNULL: @@ -147,17 +155,54 @@ protected function normalizeValue($operator, $value) } /** - * @param \APY\DataGridBundle\Grid\Column\Column[] $columns - * @param int $page Page Number - * @param int $limit Rows Per Page - * @param int $gridDataJunction Grid data junction + * Sets the initial QueryBuilder for this DataGrid. + * + * @param QueryBuilder $queryBuilder + */ + public function initQueryBuilder(QueryBuilder $queryBuilder) + { + $this->query = clone $queryBuilder; + } + + /** + * @return QueryBuilder + */ + protected function getQueryBuilder() + { + //If a custom QB has been provided, use that + //Otherwise create our own basic one + if ($this->query instanceof QueryBuilder) { + $qb = $this->query; + } else { + $qb = $this->query = $this->manager->createQueryBuilder($this->documentName); + } + + return $qb; + } + + /** + * @param ColumnsIterator $columns + * @param int $page Page Number + * @param int $limit Rows Per Page + * @param int $gridDataJunction Grid data junction + * * @return \APY\DataGridBundle\Grid\Rows */ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gridDataJunction = Column::DATA_CONJUNCTION) { - $this->query = $this->manager->createQueryBuilder($this->documentName); + $this->query = $this->getQueryBuilder(); + $validColumns = []; foreach ($columns as $column) { + + //checks if exists '.' notation on referenced columns and build query if it's filtered + $subColumn = explode('.', $column->getId()); + if (count($subColumn) > 1 && isset($this->referencedMappings[$subColumn[0]])) { + $this->addReferencedColumnn($subColumn, $column); + + continue; + } + $this->query->select($column->getField()); if ($column->isSorted()) { @@ -180,9 +225,10 @@ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gr } else { $this->query->field($column->getField())->$operator($value); } - } } + + $validColumns[] = $column; } if ($page > 0) { @@ -205,6 +251,8 @@ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gr //execute and get results $result = new Rows(); + // I really don't know if Cursor is the right type returned (I mean, every single type). + // As I didn't find out this information, I'm gonna test it with Cursor returned only. $cursor = $this->query->getQuery()->execute(); $this->count = $cursor->count(); @@ -213,14 +261,16 @@ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gr $row = new Row(); $properties = $this->getClassProperties($resource); - foreach ($columns as $column) { - if (isset($properties[$column->getId()])) { - $row->setField($column->getId(), $properties[$column->getId()]); + foreach ($validColumns as $column) { + if (isset($properties[strtolower($column->getId())])) { + $row->setField($column->getId(), $properties[strtolower($column->getId())]); } } + $this->addReferencedFields($row, $resource); + //call overridden prepareRow or associated closure - if (($modifiedRow = $this->prepareRow($row)) != null) { + if (($modifiedRow = $this->prepareRow($row)) !== null) { $result->addRow($modifiedRow); } } @@ -228,10 +278,75 @@ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gr return $result; } + /** + * @param array $subColumn + * @param Column \APY\DataGridBundle\Grid\Column\Column + */ + protected function addReferencedColumnn(array $subColumn, Column $column) + { + $this->referencedColumns[$subColumn[0]][] = $subColumn[1]; + + if ($column->isFiltered()) { + $helperQuery = $this->manager->createQueryBuilder($this->referencedMappings[$subColumn[0]]); + $filters = $column->getFilters('document'); + foreach ($filters as $filter) { + $operator = $this->normalizeOperator($filter->getOperator()); + $value = $this->normalizeValue($filter->getOperator(), $filter->getValue()); + + $helperQuery->field($subColumn[1])->$operator($value); + $this->prepareQuery($this->query); + + $cursor = $helperQuery->getQuery()->execute(); + + foreach ($cursor as $resource) { + // Is this case possible? I don't think so + if ($cursor->count() > 0) { + $this->query->select($subColumn[0]); + } + + if ($cursor->count() == 1) { + $this->query->field($subColumn[0])->references($resource); + } else { + $this->query->addOr($this->query->expr()->field($subColumn[0])->references($resource)); + } + } + } + } + } + + /** + * @param \APY\DataGridBundle\Grid\Row $row + * @param Document $resource + * + * @throws \Exception if getter for field does not exists + * + * @return \APY\DataGridBundle\Grid\Row $row with referenced fields + */ + protected function addReferencedFields(Row $row, $resource) + { + foreach ($this->referencedColumns as $parent => $subColumns) { + $node = $this->getClassProperties($resource); + if (isset($node[strtolower($parent)])) { + $node = $node[strtolower($parent)]; + + foreach ($subColumns as $field) { + $getter = 'get' . ucfirst($field); + if (method_exists($node, $getter)) { + $row->setField($parent . '.' . $field, $node->$getter()); + } else { + throw new \Exception(sprintf('Method %s for Document %s not exists', $getter, $this->referencedMappings[$parent])); + } + } + } + } + + return $row; + } + public function getTotalCount($maxResults = null) { if ($maxResults !== null) { - return min(array($maxResults, $this->count)); + return min([$maxResults, $this->count]); } return $this->count; @@ -240,24 +355,30 @@ public function getTotalCount($maxResults = null) protected function getClassProperties($obj) { $reflect = new \ReflectionClass($obj); - $props = $reflect->getProperties(); - $result = array(); + $props = $reflect->getProperties(); + $result = []; foreach ($props as $property) { $property->setAccessible(true); - $result[$property->getName()] = $property->getValue($obj); + $result[strtolower($property->getName())] = $property->getValue($obj); } return $result; } + /** + * @param string $class + * @param string $group + * + * @return array + */ public function getFieldsMetadata($class, $group = 'default') { - $result = array(); + $result = []; foreach ($this->odmMetadata->getReflectionProperties() as $property) { $name = $property->getName(); $mapping = $this->odmMetadata->getFieldMapping($name); - $values = array('title' => $name, 'source' => true); + $values = ['title' => $name, 'source' => true]; if (isset($mapping['fieldName'])) { $values['field'] = $mapping['fieldName']; @@ -286,7 +407,7 @@ public function getFieldsMetadata($class, $group = 'default') $values['type'] = 'number'; break; /*case 'hash': - $values['type'] = 'array';*/ + $values['type'] = 'array';*/ case 'boolean': $values['type'] = 'boolean'; break; @@ -299,6 +420,9 @@ public function getFieldsMetadata($class, $group = 'default') break; case 'one': $values['type'] = 'array'; + if (isset($mapping['reference']) && $mapping['reference'] === true) { + $this->referencedMappings[$name] = $mapping['targetDocument']; + } break; case 'many': $values['type'] = 'array'; @@ -315,7 +439,7 @@ public function getFieldsMetadata($class, $group = 'default') public function populateSelectFilters($columns, $loop = false) { - $queryFromSource = $this->manager->createQueryBuilder($this->documentName); + $queryFromSource = $this->getQueryBuilder(); $queryFromQuery = clone $this->query; // Clean the select fields from the query @@ -332,7 +456,7 @@ public function populateSelectFilters($columns, $loop = false) // For negative operators, show all values if ($selectFrom === 'query') { foreach ($column->getFilters('document') as $filter) { - if (in_array($filter->getOperator(), array(Column::OPERATOR_NEQ, Column::OPERATOR_NLIKE,Column::OPERATOR_NSLIKE))) { + if (in_array($filter->getOperator(), [Column::OPERATOR_NEQ, Column::OPERATOR_NLIKE, Column::OPERATOR_NSLIKE])) { $selectFrom = 'source'; break; } @@ -350,9 +474,8 @@ public function populateSelectFilters($columns, $loop = false) ->getQuery() ->execute(); - $values = array(); + $values = []; foreach ($result as $value) { - switch ($column->getType()) { case 'number': $values[$value] = $column->getDisplayedValue($value); @@ -365,7 +488,7 @@ public function populateSelectFilters($columns, $loop = false) } // Mongodb bug ? timestamp value is on the key 'i' instead of the key 't' - if (is_array($value) && array_keys($value) == array('t','i')) { + if (is_array($value) && array_keys($value) == ['t', 'i']) { $value = $value['i']; } @@ -382,12 +505,18 @@ public function populateSelectFilters($columns, $loop = false) $column->setSelectFrom('source'); $this->populateSelectFilters($columns, true); } else { + $values = $this->prepareColumnValues($column, $values); $column->setValues($values); } } } } + /** + * @param array $ids + * + * @throws \Exception + */ public function delete(array $ids) { $repository = $this->getRepository(); @@ -405,13 +534,39 @@ public function delete(array $ids) $this->manager->flush(); } + /** + * @return \Doctrine\ODM\MongoDB\DocumentRepository + */ public function getRepository() { - return$this->manager->getRepository($this->documentName); + return $this->manager->getRepository($this->documentName); } + /** + * @return string + */ public function getHash() { return $this->documentName; } + + /** + * Get the value of query + * + * @return \Doctrine\ODM\MongoDB\Query\Builder; + */ + public function getQuery() + { + return $this->query; + } + + /** + * Get the value of referencedMappings + * + * @return array + */ + public function getReferencedMappings() + { + return $this->referencedMappings; + } } diff --git a/Grid/Source/Entity.php b/Grid/Source/Entity.php index ec1ad141..3a006e65 100644 --- a/Grid/Source/Entity.php +++ b/Grid/Source/Entity.php @@ -5,6 +5,7 @@ * * (c) Abhoryo * (c) Stanislav Turza + * (c) Patryk Grudniewski * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -13,17 +14,26 @@ namespace APY\DataGridBundle\Grid\Source; use APY\DataGridBundle\Grid\Column\Column; -use APY\DataGridBundle\Grid\Rows; +use APY\DataGridBundle\Grid\Column\JoinColumn; use APY\DataGridBundle\Grid\Row; +use APY\DataGridBundle\Grid\Rows; +use Doctrine\ORM\Internal\SQLResultCasing; use Doctrine\ORM\NoResultException; use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; -use Symfony\Component\HttpKernel\Kernel; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Tools\Pagination\CountWalker; +use Doctrine\ORM\Tools\Pagination\Paginator; +use Symfony\Component\HttpKernel\Kernel; class Entity extends Source { + use SQLResultCasing; + + const DOT_DQL_ALIAS_PH = '__dot__'; + const COLON_DQL_ALIAS_PH = '__col__'; + /** * @var \Doctrine\ORM\EntityManager */ @@ -54,7 +64,6 @@ class Entity extends Source */ protected $managerName; - /** * @var \APY\DataGridBundle\Grid\Mapping\Metadata\Metadata */ @@ -90,36 +99,43 @@ class Entity extends Source * You can override this if the querybuilder is constructed in a business-specific way * by an external controller/service/repository and you wish to re-use it for the datagrid. * Typical use-case involves an external repository creating complex default restriction (i.e. multi-tenancy etc) - * which then will be expanded on by the datagrid + * which then will be expanded on by the datagrid. + * * @var QueryBuilder */ protected $queryBuilder; - /** - * The table alias that will be used in the query to fetch actual data + * The table alias that will be used in the query to fetch actual data. + * * @var string */ protected $tableAlias; + /** + * @var null + */ + protected $prepareCountQueryCallback = null; + /** * Legacy way of accessing the default alias (before it became possible to change it) - * Please use $entity->getTableAlias() now instead of $entity::TABLE_ALIAS + * Please use $entity->getTableAlias() now instead of $entity::TABLE_ALIAS. + * * @deprecated */ const TABLE_ALIAS = '_a'; /** - * @param string $entityName e.g Cms:Page + * @param string $entityName e.g Cms:Page * @param string $managerName e.g. mydatabase */ public function __construct($entityName, $group = 'default', $managerName = null) { $this->entityName = $entityName; $this->managerName = $managerName; - $this->joins = array(); + $this->joins = []; $this->group = $group; - $this->hints = array(); + $this->hints = []; $this->setTableAlias(self::TABLE_ALIAS); } @@ -134,13 +150,49 @@ public function initialise($container) $mapping = $container->get('grid.mapping.manager'); - /** todo autoregister mapping drivers with tag */ + /* todo autoregister mapping drivers with tag */ $mapping->addDriver($this, -1); $this->metadata = $mapping->getMetadata($this->class, $this->group); $this->groupBy = $this->metadata->getGroupBy(); } + /** + * @param \APY\DataGridBundle\Grid\Column\Column $column + * + * @return string + */ + protected function getTranslationFieldNameWithParents($column) + { + $name = $column->getField(); + + if ($column->getIsManualField()) { + return $column->getField(); + } + + if (strpos($name, '.') !== false) { + $previousParent = ''; + + $elements = explode('.', $name); + while ($element = array_shift($elements)) { + if (count($elements) > 0) { + $previousParent .= '_' . $element; + } + } + } elseif (strpos($name, ':') !== false) { + $previousParent = $this->getTableAlias(); + } else { + return $this->getTableAlias().'.'.$name; + } + + $matches = array(); + if ($column->hasDQLFunction($matches)) { + return $previousParent.'.'.$matches['field']; + } + + return $column->getField(); + } + /** * @param \APY\DataGridBundle\Grid\Column\Column $column * @return string @@ -149,7 +201,7 @@ protected function getFieldName($column, $withAlias = false) { $name = $column->getField(); - if($column->getIsManualField()) { + if ($column->getIsManualField()) { return $column->getField(); } @@ -161,32 +213,32 @@ protected function getFieldName($column, $withAlias = false) if (count($elements) > 0) { $parent = ($previousParent == '') ? $this->getTableAlias() : $previousParent; $previousParent .= '_' . $element; - $this->joins[$previousParent] = array('field' => $parent . '.' . $element, 'type' => $column->getJoinType()); + $this->joins[$previousParent] = ['field' => $parent . '.' . $element, 'type' => $column->getJoinType()]; } else { $name = $previousParent . '.' . $element; } } - $alias = str_replace('.', '::', $column->getId()); + $alias = $this->fromColIdToAlias($column->getId()); } elseif (strpos($name, ':') !== false) { $previousParent = $this->getTableAlias(); $alias = $name; } else { - return $this->getTableAlias().'.'.$name; + return $this->getTableAlias() . '.' . $name; } // Aggregate dql functions - $matches = array(); + $matches = []; if ($column->hasDQLFunction($matches)) { if (strtolower($matches['parameters']) == 'distinct') { - $functionWithParameters = $matches['function'].'(DISTINCT '.$previousParent.'.'.$matches['field'].')'; + $functionWithParameters = $matches['function'] . '(DISTINCT ' . $previousParent . '.' . $matches['field'] . ')'; } else { $parameters = ''; if ($matches['parameters'] !== '') { - $parameters = ', ' . (is_numeric($matches['parameters']) ? $matches['parameters'] : "'".$matches['parameters']."'"); + $parameters = ', ' . (is_numeric($matches['parameters']) ? $matches['parameters'] : "'" . $matches['parameters'] . "'"); } - $functionWithParameters = $matches['function'].'('.$previousParent.'.'.$matches['field'].$parameters.')'; + $functionWithParameters = $matches['function'] . '(' . $previousParent . '.' . $matches['field'] . $parameters . ')'; } if ($withAlias) { @@ -207,8 +259,19 @@ protected function getFieldName($column, $withAlias = false) return $name; } + /** + * @param string $colId + * + * @return string + */ + private function fromColIdToAlias($colId) + { + return str_replace(['.', ':'], [self::DOT_DQL_ALIAS_PH, self::COLON_DQL_ALIAS_PH], $colId); + } + /** * @param string $fieldName + * * @return string */ protected function getGroupByFieldName($fieldName) @@ -229,7 +292,7 @@ protected function getGroupByFieldName($fieldName) $fieldName = substr($fieldName, 0, $pos); } - return $this->getTableAlias().'.'.$fieldName; + return $this->getTableAlias() . '.' . $fieldName; } return $name; @@ -237,7 +300,6 @@ protected function getGroupByFieldName($fieldName) /** * @param \APY\DataGridBundle\Grid\Columns $columns - * @return null */ public function getColumns($columns) { @@ -258,7 +320,7 @@ protected function normalizeOperator($operator) case Column::OPERATOR_LSLIKE: case Column::OPERATOR_RSLIKE: case Column::OPERATOR_NSLIKE: - return 'like'; + return 'like'; default: return $operator; } @@ -285,14 +347,16 @@ protected function normalizeValue($operator, $value) } /** - * Sets the initial QueryBuilder for this DataGrid + * Sets the initial QueryBuilder for this DataGrid. + * * @param QueryBuilder $queryBuilder */ public function initQueryBuilder(QueryBuilder $queryBuilder) { $this->queryBuilder = clone $queryBuilder; - //Try to guess the new root alias and apply it to our queries+ //as the external querybuilder almost certainly is not used our default alias + //Try to guess the new root alias and apply it to our queries+ + //as the external querybuilder almost certainly is not used our default alias $externalTableAliases = $this->queryBuilder->getRootAliases(); if (count($externalTableAliases)) { $this->setTableAlias($externalTableAliases[0]); @@ -304,10 +368,10 @@ public function initQueryBuilder(QueryBuilder $queryBuilder) */ protected function getQueryBuilder() { - //If a custom QB has been provided, use that + //If a custom QB has been provided, use a copy of that one //Otherwise create our own basic one if ($this->queryBuilder instanceof QueryBuilder) { - $qb = $this->queryBuilder; + $qb = clone $this->queryBuilder; } else { $qb = $this->manager->createQueryBuilder($this->class); $qb->from($this->class, $this->getTableAlias()); @@ -318,9 +382,10 @@ protected function getQueryBuilder() /** * @param \APY\DataGridBundle\Grid\Column\Column[] $columns - * @param int $page Page Number - * @param int $limit Rows Per Page - * @param int $gridDataJunction Grid data junction + * @param int $page Page Number + * @param int $limit Rows Per Page + * @param int $gridDataJunction Grid data junction + * * @return \APY\DataGridBundle\Grid\Rows */ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gridDataJunction = Column::DATA_CONJUNCTION) @@ -329,26 +394,26 @@ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gr $this->querySelectfromSource = clone $this->query; $bindIndex = 123; - $serializeColumns = array(); + $serializeColumns = []; $where = $gridDataJunction === Column::DATA_CONJUNCTION ? $this->query->expr()->andx() : $this->query->expr()->orx(); - $columnsById = array(); + $columnsById = []; foreach ($columns as $column) { $columnsById[$column->getId()] = $column; } foreach ($columns as $column) { - // If a column is a manual field, ie a.col*b.col as myfield, it is added to select from user. - if($column->getIsManualField() === false) { + if ($column->getIsManualField() === false) { $fieldName = $this->getFieldName($column, true); $this->query->addSelect($fieldName); $this->querySelectfromSource->addSelect($fieldName); } if ($column->isSorted()) { - if ($column->getType() === 'join') { - foreach($column->getJoinColumns() as $columnName) { + if ($column instanceof JoinColumn) { + $this->query->resetDQLPart('orderBy'); + foreach ($column->getJoinColumns() as $columnName) { $this->query->addOrderBy($this->getFieldName($columnsById[$columnName]), $column->getOrder()); } } else { @@ -362,22 +427,35 @@ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gr $isDisjunction = $column->getDataJunction() === Column::DATA_DISJUNCTION; - $hasHavingClause = $column->hasDQLFunction() || $column->getIsAggregate(); + $dqlMatches = []; + $hasHavingClause = $column->hasDQLFunction($dqlMatches) || $column->getIsAggregate(); + if(isset($dqlMatches['function']) && $dqlMatches['function'] == 'translation_agg'){ + $hasHavingClause = false; + } $sub = $isDisjunction ? $this->query->expr()->orx() : ($hasHavingClause ? $this->query->expr()->andx() : $where); foreach ($filters as $filter) { $operator = $this->normalizeOperator($filter->getOperator()); - $columnForFilter = ($column->getType() !== 'join') ? $column : $columnsById[$filter->getColumnName()]; + $columnForFilter = (!$column instanceof JoinColumn) ? $column : $columnsById[$filter->getColumnName()]; $fieldName = $this->getFieldName($columnForFilter, false); $bindIndexPlaceholder = "?$bindIndex"; + if( in_array($filter->getOperator(), array(Column::OPERATOR_LIKE,Column::OPERATOR_RLIKE,Column::OPERATOR_LLIKE,Column::OPERATOR_NLIKE,))){ - $fieldName = "LOWER($fieldName)"; + if(isset($dqlMatches['function']) && $dqlMatches['function'] == 'translation_agg'){ + $translationFieldName = $this->getTranslationFieldNameWithParents($columnForFilter); + $fieldName = "LOWER(".$translationFieldName.")"; + }elseif(isset($dqlMatches['function']) && $dqlMatches['function'] == 'role_agg'){ + $translationFieldName = $this->getTranslationFieldNameWithParents($columnForFilter); + $fieldName = "LOWER(".$translationFieldName.")"; + }else{ + $fieldName = "LOWER($fieldName)"; + } $bindIndexPlaceholder = "LOWER($bindIndexPlaceholder)"; } - + $q = $this->query->expr()->$operator($fieldName, $bindIndexPlaceholder); if ($filter->getOperator() == Column::OPERATOR_NLIKE || $filter->getOperator() == Column::OPERATOR_NSLIKE) { @@ -403,7 +481,7 @@ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gr } } - if ($where->count()> 0) { + if ($where->count() > 0) { //Using ->andWhere here to make sure we preserve any other where clauses present in the query builder //the other where clauses may have come from an external builder $this->query->andWhere($where); @@ -446,12 +524,14 @@ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gr //call overridden prepareQuery or associated closure $this->prepareQuery($this->query); + $hasJoin = $this->checkIfQueryHasFetchJoin($this->query); $query = $this->query->getQuery(); foreach ($this->hints as $hintKey => $hintValue) { $query->setHint($hintKey, $hintValue); } - $items = $query->getResult(); + $items = new Paginator($query, $hasJoin); + $items->setUseOutputWalkers(false); $repository = $this->manager->getRepository($this->entityName); @@ -472,7 +552,7 @@ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gr $row = new Row(); foreach ($item as $key => $value) { - $key = str_replace('::', '.', $key); + $key = $this->fromAliasToColId($key); if (in_array($key, $serializeColumns) && is_string($value)) { $value = unserialize($value); @@ -487,7 +567,7 @@ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gr $row->setRepository($repository); //call overridden prepareRow or associated closure - if (($modifiedRow = $this->prepareRow($row)) != null) { + if (($modifiedRow = $this->prepareRow($row)) !== null) { $result->addRow($modifiedRow); } } @@ -495,10 +575,23 @@ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gr return $result; } + /** + * @param string $alias + * + * @return string + */ + private function fromAliasToColId($alias) + { + return str_replace([self::DOT_DQL_ALIAS_PH, self::COLON_DQL_ALIAS_PH], ['.', ':'], $alias); + } + public function getTotalCount($maxResults = null) { // Doctrine Bug Workaround: http://www.doctrine-project.org/jira/browse/DDC-1927 $countQueryBuilder = clone $this->query; + + $this->prepareCountQuery($countQueryBuilder); + foreach ($countQueryBuilder->getRootAliases() as $alias) { $countQueryBuilder->addSelect($alias); } @@ -511,15 +604,15 @@ public function getTotalCount($maxResults = null) $countQuery->setHint($hintName, $hintValue); } - if (! $countQuery->getHint(CountWalker::HINT_DISTINCT)) { + if (!$countQuery->getHint(CountWalker::HINT_DISTINCT)) { $countQuery->setHint(CountWalker::HINT_DISTINCT, true); } - if ($countQuery->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) == false) { + if ($countQuery->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER) === false) { $platform = $countQuery->getEntityManager()->getConnection()->getDatabasePlatform(); // law of demeter win $rsm = new ResultSetMapping(); - $rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count'); + $rsm->addScalarResult($this->getSQLResultCasing($platform,'dctrn_count'), 'count'); $countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'Doctrine\ORM\Tools\Pagination\CountOutputWalker'); $countQuery->setResultSetMapping($rsm); @@ -527,14 +620,14 @@ public function getTotalCount($maxResults = null) $hints = $countQuery->getHint(Query::HINT_CUSTOM_TREE_WALKERS); if ($hints === false) { - $hints = array(); + $hints = []; } $hints[] = 'Doctrine\ORM\Tools\Pagination\CountWalker'; //$hints[] = 'APY\DataGridBundle\Grid\Helper\ORMCountWalker'; $countQuery->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $hints); } - $countQuery->setFirstResult(null)->setMaxResults($maxResults); + $countQuery->setFirstResult(0)->setMaxResults($maxResults); try { $data = $countQuery->getScalarResult(); @@ -549,10 +642,10 @@ public function getTotalCount($maxResults = null) public function getFieldsMetadata($class, $group = 'default') { - $result = array(); + $result = []; foreach ($this->ormMetadata->getFieldNames() as $name) { $mapping = $this->ormMetadata->getFieldMapping($name); - $values = array('title' => $name, 'source' => true); + $values = ['title' => $name, 'source' => true]; if (isset($mapping['fieldName'])) { $values['field'] = $mapping['fieldName']; @@ -610,7 +703,7 @@ public function populateSelectFilters($columns, $loop = false) // For negative operators, show all values if ($selectFrom === 'query') { foreach ($column->getFilters('entity') as $filter) { - if (in_array($filter->getOperator(), array(Column::OPERATOR_NEQ, Column::OPERATOR_NLIKE,Column::OPERATOR_NSLIKE))) { + if (in_array($filter->getOperator(), [Column::OPERATOR_NEQ, Column::OPERATOR_NLIKE, Column::OPERATOR_NSLIKE])) { $selectFrom = 'source'; break; } @@ -620,17 +713,24 @@ public function populateSelectFilters($columns, $loop = false) // Dynamic from query or not ? $query = ($selectFrom === 'source') ? clone $this->querySelectfromSource : clone $this->query; - $result = $query->select($this->getFieldName($column, true)) + $query = $query->select($this->getFieldName($column, true)) ->distinct() ->orderBy($this->getFieldName($column), 'asc') ->setFirstResult(null) ->setMaxResults(null) - ->getQuery() - ->getResult(); + ->getQuery(); + if ($selectFrom === 'query') { + foreach ($this->hints as $hintKey => $hintValue) { + $query->setHint($hintKey, $hintValue); + } + } + $result = $query->getResult(); - $values = array(); + $values = []; foreach ($result as $row) { - $value = $row[str_replace('.', '::', $column->getId())]; + $alias = $this->fromColIdToAlias($column->getId()); + + $value = $row[$alias]; switch ($column->getType()) { case 'array': @@ -641,6 +741,14 @@ public function populateSelectFilters($columns, $loop = false) $values[$val] = $val; } break; + case 'simple_array': + if (is_string($value)) { + $value = explode(',', $value); + } + foreach ($value as $val) { + $values[$val] = $val; + } + break; case 'number': $values[$value] = $column->getDisplayedValue($value); break; @@ -664,12 +772,35 @@ public function populateSelectFilters($columns, $loop = false) natcasesort($values); } + $values = $this->prepareColumnValues($column, $values); $column->setValues($values); } } } } + /** + * @param QueryBuilder $countQueryBuilder + */ + public function prepareCountQuery(QueryBuilder $countQueryBuilder) + { + if (is_callable($this->prepareCountQueryCallback)) { + call_user_func($this->prepareCountQueryCallback, $countQueryBuilder); + } + } + + /** + * @param callable $callback + * + * @return $this + */ + public function manipulateCountQuery($callback = null) + { + $this->prepareCountQueryCallback = $callback; + + return $this; + } + public function delete(array $ids) { $repository = $this->getRepository(); @@ -704,11 +835,12 @@ public function addHint($key, $value) public function clearHints() { - $this->hints = array(); + $this->hints = []; } /** - * Set groupby column + * Set groupby column. + * * @param string $groupBy GroupBy column */ public function setGroupBy($groupBy) @@ -736,5 +868,24 @@ public function getTableAlias() { return $this->tableAlias; } - + + /** + * @param QueryBuilder $qb + * @return boolean + */ + protected function checkIfQueryHasFetchJoin(QueryBuilder $qb) + { + $join = $qb->getDqlPart('join'); + if (empty($join)) { + return false; + } + + foreach ($join[$this->getTableAlias()] as $join) { + if ($join->getJoinType() === Join::INNER_JOIN || $join->getJoinType() === Join::LEFT_JOIN) { + return true; + } + } + + return false; + } } diff --git a/Grid/Source/Source.php b/Grid/Source/Source.php old mode 100755 new mode 100644 index 56921bbb..8d685548 --- a/Grid/Source/Source.php +++ b/Grid/Source/Source.php @@ -12,18 +12,19 @@ namespace APY\DataGridBundle\Grid\Source; +use APY\DataGridBundle\Grid\Column\Column; +use APY\DataGridBundle\Grid\Exception\PropertyAccessDeniedException; +use APY\DataGridBundle\Grid\Helper\ColumnsIterator; use APY\DataGridBundle\Grid\Mapping\Driver\DriverInterface; -use Symfony\Component\Form\Exception\PropertyAccessDeniedException; -use APY\DataGridBundle\Grid\Column; -use APY\DataGridBundle\Grid\Rows; use APY\DataGridBundle\Grid\Row; +use APY\DataGridBundle\Grid\Rows; abstract class Source implements DriverInterface { protected $prepareQueryCallback = null; protected $prepareRowCallback = null; protected $data = null; - protected $items = array(); + protected $items = []; protected $count; /** @@ -38,6 +39,7 @@ public function prepareQuery($queryBuilder) /** * @param \APY\DataGridBundle\Grid\Row $row + * * @return \APY\DataGridBundle\Grid\Row|null */ public function prepareRow($row) @@ -51,6 +53,7 @@ public function prepareRow($row) /** * @param callable $callback + * * @return $this */ public function manipulateQuery($callback = null) @@ -71,77 +74,82 @@ public function manipulateRow(\Closure $callback = null) } /** - * Find data for current page + * Find data for current page. * * @abstract - * @param \APY\DataGridBundle\Grid\Column\Column[] $columns - * @param int $page Page Number - * @param int $limit Rows Per Page - * @param int $gridDataJunction Grid data junction + * + * @param ColumnsIterator $columns + * @param int $page Page Number + * @param int $limit Rows Per Page + * @param int $gridDataJunction Grid data junction + * * @return \APY\DataGridBundle\Grid\Rows */ + // @todo: typehint? abstract public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gridDataJunction = Column::DATA_CONJUNCTION); /** - * Get Total count of data items + * Get Total count of data items. * * @param int $maxResults + * * @return int */ abstract public function getTotalCount($maxResults = null); /** - * Set container + * Set container. * * @abstract + * * @param $container - * @return void */ abstract public function initialise($container); /** * @abstract + * * @param $columns */ abstract public function getColumns($columns); public function getClassColumns($class, $group = 'default') { - return array(); + return []; } public function getFieldsMetadata($class, $group = 'default') { - return array(); + return []; } public function getGroupBy($class, $group = 'default') { - return array(); + return []; } abstract public function populateSelectFilters($columns, $loop = false); /** - * Return source hash string - * @abstract - */ + * Return source hash string. + * + * @abstract + */ abstract public function getHash(); /** - * Delete one or more objects + * Delete one or more objects. * * @abstract + * * @param array $ids - * @return void */ abstract public function delete(array $ids); /** - * Use data instead of fetching the source + * Use data instead of fetching the source. * * @param array|object $data - * @return void */ public function setData($data) { @@ -151,7 +159,7 @@ public function setData($data) } /** - * Get the loaded data + * Get the loaded data. * * @return array|object */ @@ -161,9 +169,9 @@ public function getData() } /** - * Check if data is loaded + * Check if data is loaded. * - * @return boolean + * @return bool */ public function isDataLoaded() { @@ -171,13 +179,13 @@ public function isDataLoaded() } /** - * Gets an array of data items for rows from the set data + * Gets an array of data items for rows from the set data. * * @return array */ protected function getItemsFromData($columns) { - $items = array(); + $items = []; foreach ($this->data as $key => $item) { foreach ($columns as $column) { @@ -194,7 +202,7 @@ protected function getItemsFromData($columns) $elements = explode('.', $fieldName); while ($element = array_shift($elements)) { if (count($elements) > 0) { - $itemEntity = call_user_func(array($itemEntity, 'get'.$element)); + $itemEntity = call_user_func([$itemEntity, 'get' . $element]); } else { $functionName = ucfirst($element); } @@ -204,10 +212,10 @@ protected function getItemsFromData($columns) // Get value of the column if (isset($itemEntity->$fieldName)) { $fieldValue = $itemEntity->$fieldName; - } elseif (is_callable(array($itemEntity, $fullFunctionName = 'get'.$functionName)) - || is_callable(array($itemEntity, $fullFunctionName = 'has'.$functionName)) - || is_callable(array($itemEntity, $fullFunctionName = 'is'.$functionName))) { - $fieldValue = call_user_func(array($itemEntity, $fullFunctionName)); + } elseif (is_callable([$itemEntity, $fullFunctionName = 'get' . $functionName]) + || is_callable([$itemEntity, $fullFunctionName = 'has' . $functionName]) + || is_callable([$itemEntity, $fullFunctionName = 'is' . $functionName])) { + $fieldValue = call_user_func([$itemEntity, $fullFunctionName]); } else { throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public or has no accessor.', $fieldName)); } @@ -223,21 +231,22 @@ protected function getItemsFromData($columns) } /** - * Find data from array|object + * Find data from array|object. + * + * @param Column[] $columns + * @param int $page + * @param int $limit + * @param int $maxResults * - * @param \APY\DataGridBundle\Grid\Column\Column[] $columns - * @param int $page - * @param int $limit - * @return \APY\DataGridBundle\DataGrid\Rows + * @return Rows */ public function executeFromData($columns, $page = 0, $limit = 0, $maxResults = null) { // Populate from data $items = $this->getItemsFromData($columns); - $serializeColumns = array(); + $serializeColumns = []; foreach ($this->data as $key => $item) { - $keep = true; foreach ($columns as $column) { $fieldName = $column->getField(); @@ -253,7 +262,7 @@ public function executeFromData($columns, $page = 0, $limit = 0, $maxResults = n // Some attributes of the column can be changed in this function $filters = $column->getFilters('vector'); - if ($column->getDataJunction() === Column\Column::DATA_DISJUNCTION) { + if ($column->getDataJunction() === Column::DATA_DISJUNCTION) { $disjunction = true; $keep = false; } else { @@ -270,79 +279,79 @@ public function executeFromData($columns, $page = 0, $limit = 0, $maxResults = n if (!$dataIsNumeric && !($value instanceof \DateTime)) { $value = $this->prepareStringForLikeCompare($value); switch ($operator) { - case Column\Column::OPERATOR_EQ: - $value = '/^'.preg_quote($value, '/').'$/i'; + case Column::OPERATOR_EQ: + $value = '/^' . preg_quote($value, '/') . '$/i'; break; - case Column\Column::OPERATOR_NEQ: - $value = '/^(?!'.preg_quote($value, '/').'$).*$/i'; + case Column::OPERATOR_NEQ: + $value = '/^(?!' . preg_quote($value, '/') . '$).*$/i'; break; - case Column\Column::OPERATOR_LIKE: - $value = '/'.preg_quote($value, '/').'/i'; + case Column::OPERATOR_LIKE: + $value = '/' . preg_quote($value, '/') . '/i'; break; - case Column\Column::OPERATOR_NLIKE: - $value = '/^((?!'.preg_quote($value, '/').').)*$/i'; + case Column::OPERATOR_NLIKE: + $value = '/^((?!' . preg_quote($value, '/') . ').)*$/i'; break; - case Column\Column::OPERATOR_LLIKE: - $value = '/'.preg_quote($value, '/').'$/i'; + case Column::OPERATOR_LLIKE: + $value = '/' . preg_quote($value, '/') . '$/i'; break; - case Column\Column::OPERATOR_RLIKE: - $value = '/^'.preg_quote($value, '/').'/i'; + case Column::OPERATOR_RLIKE: + $value = '/^' . preg_quote($value, '/') . '/i'; break; - case Column\Column::OPERATOR_SLIKE: - $value = '/'.preg_quote($value, '/').'/'; + case Column::OPERATOR_SLIKE: + $value = '/' . preg_quote($value, '/') . '/'; break; - case Column\Column::OPERATOR_NSLIKE: - $value = '/^((?!'.preg_quote($value, '/').').)*$/'; + case Column::OPERATOR_NSLIKE: + $value = '/^((?!' . preg_quote($value, '/') . ').)*$/'; break; - case Column\Column::OPERATOR_LSLIKE: - $value = '/'.preg_quote($value, '/').'$/'; + case Column::OPERATOR_LSLIKE: + $value = '/' . preg_quote($value, '/') . '$/'; break; - case Column\Column::OPERATOR_RSLIKE: - $value = '/^'.preg_quote($value, '/').'/'; + case Column::OPERATOR_RSLIKE: + $value = '/^' . preg_quote($value, '/') . '/'; break; } } // Test switch ($operator) { - case Column\Column::OPERATOR_EQ: + case Column::OPERATOR_EQ: if ($dataIsNumeric) { - $found = abs($fieldValue-$value) < 0.00001; + $found = abs($fieldValue - $value) < 0.00001; break; } - case Column\Column::OPERATOR_NEQ: + case Column::OPERATOR_NEQ: if ($dataIsNumeric) { - $found = abs($fieldValue-$value) > 0.00001; + $found = abs($fieldValue - $value) > 0.00001; break; } - case Column\Column::OPERATOR_LIKE: - case Column\Column::OPERATOR_NLIKE: - case Column\Column::OPERATOR_LLIKE: - case Column\Column::OPERATOR_RLIKE: - case Column\Column::OPERATOR_SLIKE: - case Column\Column::OPERATOR_NSLIKE: - case Column\Column::OPERATOR_LSLIKE: - case Column\Column::OPERATOR_RSLIKE: + case Column::OPERATOR_LIKE: + case Column::OPERATOR_NLIKE: + case Column::OPERATOR_LLIKE: + case Column::OPERATOR_RLIKE: + case Column::OPERATOR_SLIKE: + case Column::OPERATOR_NSLIKE: + case Column::OPERATOR_LSLIKE: + case Column::OPERATOR_RSLIKE: $fieldValue = $this->prepareStringForLikeCompare($fieldValue, $column->getType()); $found = preg_match($value, $fieldValue); break; - case Column\Column::OPERATOR_GT: + case Column::OPERATOR_GT: $found = $fieldValue > $value; break; - case Column\Column::OPERATOR_GTE: + case Column::OPERATOR_GTE: $found = $fieldValue >= $value; break; - case Column\Column::OPERATOR_LT: + case Column::OPERATOR_LT: $found = $fieldValue < $value; break; - case Column\Column::OPERATOR_LTE: + case Column::OPERATOR_LTE: $found = $fieldValue <= $value; break; - case Column\Column::OPERATOR_ISNULL: + case Column::OPERATOR_ISNULL: $found = $fieldValue === null; break; - case Column\Column::OPERATOR_ISNOTNULL: + case Column::OPERATOR_ISNOTNULL: $found = $fieldValue !== null; break; } @@ -372,7 +381,7 @@ public function executeFromData($columns, $page = 0, $limit = 0, $maxResults = n foreach ($columns as $column) { if ($column->isSorted()) { $sortType = SORT_REGULAR; - $sortedItems = array(); + $sortedItems = []; foreach ($items as $key => $item) { $value = $item[$column->getField()]; @@ -434,7 +443,7 @@ public function executeFromData($columns, $page = 0, $limit = 0, $maxResults = n $row = new Row(); if ($this instanceof Vector) { - $row->setPrimaryField($this->id); + $row->setPrimaryField($this->getId()); } foreach ($item as $fieldName => $fieldValue) { @@ -471,7 +480,7 @@ public function populateSelectFiltersFromData($columns, $loop = false) // For negative operators, show all values if ($selectFrom === 'query') { foreach ($column->getFilters('vector') as $filter) { - if (in_array($filter->getOperator(), array(Column\Column::OPERATOR_NEQ, Column\Column::OPERATOR_NLIKE,Column\Column::OPERATOR_NSLIKE))) { + if (in_array($filter->getOperator(), [Column::OPERATOR_NEQ, Column::OPERATOR_NLIKE, Column::OPERATOR_NSLIKE])) { $selectFrom = 'source'; break; } @@ -481,7 +490,7 @@ public function populateSelectFiltersFromData($columns, $loop = false) // Dynamic from query or not ? $item = ($selectFrom === 'source') ? $this->data : $this->items; - $values = array(); + $values = []; foreach ($item as $row) { $value = $row[$column->getField()]; @@ -498,7 +507,7 @@ public function populateSelectFiltersFromData($columns, $loop = false) } // Mongodb bug ? timestamp value is on the key 'i' instead of the key 't' - if (is_array($value) && array_keys($value) == array('t','i')) { + if (is_array($value) && array_keys($value) == ['t', 'i']) { $value = $value['i']; } @@ -528,6 +537,7 @@ public function populateSelectFiltersFromData($columns, $loop = false) natcasesort($values); } + $values = $this->prepareColumnValues($column, $values); $column->setValues(array_unique($values)); } } @@ -535,7 +545,7 @@ public function populateSelectFiltersFromData($columns, $loop = false) } /** - * Get Total count of data items + * Get Total count of data items. * * @return int */ @@ -546,9 +556,11 @@ public function getTotalCountFromData($maxResults = null) /** * Prepares string to have almost the same behaviour as with a database, - * removing accents and latin special chars - * @param mixed $inputString - * @param string $type for array type, will serialize datas + * removing accents and latin special chars. + * + * @param mixed $inputString + * @param string $type for array type, will serialize datas + * * @return string the input, serialized for arrays or without accents for strings */ protected function prepareStringForLikeCompare($input, $type = null) @@ -558,14 +570,29 @@ protected function prepareStringForLikeCompare($input, $type = null) } else { $outputString = $this->removeAccents($input); } + return $outputString; } private function removeAccents($str) { - $entStr = htmlentities($str, ENT_NOQUOTES, "UTF-8"); + if (!is_string($str)) { + return $str; // Retourne l'entrée telle quelle si ce n'est pas une chaîne + } + $entStr = htmlentities($str, ENT_NOQUOTES, 'UTF-8'); $noaccentStr = preg_replace('#&([A-za-z])(?:acute|cedil|circ|grave|orn|ring|slash|th|tilde|uml);#', '\1', $entStr); return preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $noaccentStr); } + + protected function prepareColumnValues(Column $column, $values) + { + $existingValues = $column->getValues(); + if (!empty($existingValues)) { + $intersect = array_intersect_key($existingValues, $values); + $values = array_replace($values, $intersect); + } + + return $values; + } } diff --git a/Grid/Source/Vector.php b/Grid/Source/Vector.php index 7e6e7560..ce2c2657 100644 --- a/Grid/Source/Vector.php +++ b/Grid/Source/Vector.php @@ -12,10 +12,19 @@ namespace APY\DataGridBundle\Grid\Source; -use APY\DataGridBundle\Grid\Column; +use APY\DataGridBundle\Grid\Column\ArrayColumn; +use APY\DataGridBundle\Grid\Column\BooleanColumn; +use APY\DataGridBundle\Grid\Column\Column; +use APY\DataGridBundle\Grid\Column\DateColumn; +use APY\DataGridBundle\Grid\Column\DateTimeColumn; +use APY\DataGridBundle\Grid\Column\NumberColumn; +use APY\DataGridBundle\Grid\Column\TextColumn; +use APY\DataGridBundle\Grid\Column\UntypedColumn; +use APY\DataGridBundle\Grid\Rows; /** - * Vector is really an Array + * Vector is really an Array. + * * @author dellamowica */ class Vector extends Source @@ -23,26 +32,30 @@ class Vector extends Source /** * @var array */ - protected $data = array(); + protected $data = []; /** * either a column name as a string - * or an array of names of columns + * or an array of names of columns. + * * @var mixed */ protected $id = null; /** - * Array of columns + * Array of columns. + * * @var Column[] */ protected $columns; /** - * Creates the Vector and sets its data + * Creates the Vector and sets its data. + * * @param array $data + * @param array $columns */ - public function __construct(array $data, array $columns = array()) + public function __construct(array $data, array $columns = []) { if (!empty($data)) { $this->setData($data); @@ -60,21 +73,21 @@ public function initialise($container) protected function guessColumns() { - $guessedColumns = array(); + $guessedColumns = []; $dataColumnIds = array_keys(reset($this->data)); foreach ($dataColumnIds as $id) { if (!$this->hasColumn($id)) { - $params = array( - 'id' => $id, - 'title' => $id, - 'source' => true, + $params = [ + 'id' => $id, + 'title' => $id, + 'source' => true, 'filterable' => true, - 'sortable' => true, - 'visible' => true, - 'field' => $id, - ); - $guessedColumns[] = new Column\UntypedColumn($params); + 'sortable' => true, + 'visible' => true, + 'field' => $id, + ]; + $guessedColumns[] = new UntypedColumn($params); } } @@ -84,12 +97,12 @@ protected function guessColumns() $iteration = min(10, count($this->data)); foreach ($this->columns as $c) { - if (!$c instanceof Column\UntypedColumn) { + if (!$c instanceof UntypedColumn) { continue; } $i = 0; - $fieldTypes = array(); + $fieldTypes = []; foreach ($this->data as $row) { if (!isset($row[$c->getId()])) { @@ -101,13 +114,12 @@ protected function guessColumns() if ($fieldValue !== '' && $fieldValue !== null) { if (is_array($fieldValue)) { $fieldTypes['array'] = 1; - } elseif($fieldValue instanceof \DateTime) { + } elseif ($fieldValue instanceof \DateTime) { if ($fieldValue->format('His') === '000000') { $fieldTypes['date'] = 1; } else { $fieldTypes['datetime'] = 1; } - } elseif (strlen($fieldValue) >= 3 && strtotime($fieldValue) !== false) { $dt = new \DateTime($fieldValue); if ($dt->format('His') === '000000') { @@ -143,33 +155,32 @@ protected function guessColumns() /** * @param \APY\DataGridBundle\Grid\Columns $columns - * @return null */ public function getColumns($columns) { $token = empty($this->id); //makes the first column primary by default foreach ($this->columns as $c) { - if ($c instanceof Column\UntypedColumn) { + if ($c instanceof UntypedColumn) { switch ($c->getType()) { case 'date': - $column = new Column\DateColumn($c->getParams()); + $column = new DateColumn($c->getParams()); break; case 'datetime': - $column = new Column\DateTimeColumn($c->getParams()); + $column = new DateTimeColumn($c->getParams()); break; case 'boolean': - $column = new Column\BooleanColumn($c->getParams()); + $column = new BooleanColumn($c->getParams()); break; case 'number': - $column = new Column\NumberColumn($c->getParams()); + $column = new NumberColumn($c->getParams()); break; case 'array': - $column = new Column\ArrayColumn($c->getParams()); + $column = new ArrayColumn($c->getParams()); break; case 'text': default: - $column = new Column\TextColumn($c->getParams()); + $column = new TextColumn($c->getParams()); break; } } else { @@ -188,10 +199,12 @@ public function getColumns($columns) /** * @param \APY\DataGridBundle\Grid\Column\Column[] $columns - * @param int $page Page Number - * @param int $limit Rows Per Page - * @param int $gridDataJunction Grid data junction - * @return \APY\DataGridBundle\Grid\Rows + * @param int $page Page Number + * @param int $limit Rows Per Page + * @param int $maxResults Max rows + * @param int $gridDataJunction Grid data junction + * + * @return Rows */ public function execute($columns, $page = 0, $limit = 0, $maxResults = null, $gridDataJunction = Column::DATA_CONJUNCTION) { @@ -210,11 +223,12 @@ public function getTotalCount($maxResults = null) public function getHash() { - return __CLASS__.md5(implode('', array_map(function ($c) { return $c->getId(); }, $this->columns))); + return __CLASS__ . md5(implode('', array_map(fn($c) => $c->getId(), $this->columns))); } /** - * sets the primary key + * sets the primary key. + * * @param mixed $id either a string or an array of strings */ public function setId($id) @@ -223,8 +237,18 @@ public function setId($id) } /** - * Set a two-dimentional array + * @return mixed + */ + public function getId() + { + return $this->id; + } + + /** + * Set a two-dimentional array. + * * @param array $data + * * @throws \InvalidArgumentException */ public function setData($data) @@ -235,12 +259,14 @@ public function setData($data) throw new \InvalidArgumentException('Data should be an array with content'); } + // This seems to exclude ... if (is_object(reset($this->data))) { foreach ($this->data as $key => $object) { $this->data[$key] = (array) $object; } } + // ... this other (or vice versa) $firstRaw = reset($this->data); if (!is_array($firstRaw) || empty($firstRaw)) { throw new \InvalidArgumentException('Data should be a two-dimentional array'); @@ -266,13 +292,4 @@ protected function hasColumn($id) return false; } - - protected function getColumn($id) - { - foreach ($this->columns as $c) { - if ($id === $c->getId()) { - return $c; - } - } - } } diff --git a/Grid/Type/GridType.php b/Grid/Type/GridType.php new file mode 100644 index 00000000..fda9d057 --- /dev/null +++ b/Grid/Type/GridType.php @@ -0,0 +1,92 @@ +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'; + } +} diff --git a/LICENSE b/LICENSE index 969fc781..c2f017f0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011-2014 Stanislav Turza - Abhoryo +Copyright (c) 2011-2017 Stanislav Turza - Abhoryo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/README.md b/README.md index 4d2649ab..503a98f0 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,50 @@ -Datagrid for Symfony2 inspired by Zfdatagrid and Magento Grid. -This bundle was initiated by Stanislav Turza (Sorien). -**Version**: 2.1-dev - master-dev -**Compatibility**: Symfony >= 2.0.0, Twig >= 1.5.0 +# APYDataGrid Bundle -[![Build Status](https://secure.travis-ci.org/Abhoryo/APYDataGridBundle.png?branch=master)](http://travis-ci.org/Abhoryo/APYDataGridBundle) +This **Symfony Bundle** allows you to create wonderful grid based on data or entities of your projet. -See [CHANGELOG](https://github.com/Abhoryo/APYDataGridBundle/blob/master/CHANGELOG.md) and [UPGRADE 2.0](https://github.com/Abhoryo/APYDataGridBundle/blob/master/UPGRADE-2.0.md) +[![Build Status](https://secure.travis-ci.org/APY/APYDataGridBundle.png?branch=master)](http://travis-ci.org/APY/APYDataGridBundle) [![Coverage Status](https://coveralls.io/repos/github/APY/APYDataGridBundle/badge.svg?branch=test-improvement)](https://coveralls.io/github/APY/APYDataGridBundle?branch=test-improvement) ## Features - -- Supports Entity (ORM), Document (ODM) and Vector (Array) sources -- Sortable and Filterable with operators (Comparison operators, range, starts/ends with, (not) contains, is (not) defined, regex) +This bundle allow you to create listing with many features that you can expect : +- Various data sources : supports **Entity** (ORM), **Document** (ODM) and **Vector** (Array) sources +- Data manipulation : **Sortable** and **Filterable** with many operators - Auto-typing columns (Text, Number, Boolean, Array, DateTime, Date, ...) -- Locale support for DateTime, Date and Number columns (Decimal, Currency, Percent, Duration, Scientific, Spell out) +- Locale support for columns and data (DateTime, Date and Number columns) - Input, Select, checkbox and radio button filters filled with the data of the grid or an array of values - Export (CSV, Excel, _PDF_, XML, JSON, HTML, ...) -- Mass actions -- Row actions +- Mass actions, Row actions - Supports mapped fields with Entity source - Securing the columns, actions and export with security roles - Annotations and PHP configuration - External filters box - Ajax loading - Pagination (You can also use Pagerfanta) -- Column width and column align -- Prefix translated titles - Grid manager for multi-grid on the same page - Groups configuration for ORM and ODM sources -- Easy templates overriding (twig) +- Easy templates overriding (Twig) - Custom columns and filters creation -- ... +- *and many more* -## Documentation +## Installation, documentation -See the [summary](https://github.com/Abhoryo/APYDataGridBundle/blob/master/Resources/doc/summary.md). +See the [summary](https://github.com/APY/APYDataGridBundle/blob/master/Resources/doc/summary.md). ## Screenshot -Full example with this [CSS style file](https://github.com/Abhoryo/APYDataGridBundle/blob/master/Resources/doc/grid_configuration/working_example.css): +Full example with this [CSS style file](https://github.com/APY/APYDataGridBundle/blob/master/Resources/doc/grid_configuration/working_example.css): -![test](https://github.com/Abhoryo/APYDataGridBundle/blob/master/Resources/doc/images/screenshot_full.png?raw=true) +![test](https://github.com/APY/APYDataGridBundle/blob/master/Resources/doc/images/screenshot_full.png?raw=true) Simple example with the external filter box in english: -![test](https://github.com/Abhoryo/APYDataGridBundle/blob/master/Resources/doc/images/screenshot_en.png?raw=true) +![test](https://github.com/APY/APYDataGridBundle/blob/master/Resources/doc/images/screenshot_en.png) Same example in french: -![test](https://github.com/Abhoryo/APYDataGridBundle/blob/master/Resources/doc/images/screenshot_fr.png?raw=true) - -Data used in these screenshots (this is a phpMyAdmin screenshot): +![test](https://github.com/APY/APYDataGridBundle/blob/master/Resources/doc/images/screenshot_fr.png?raw=true) -![test](https://github.com/Abhoryo/APYDataGridBundle/blob/master/Resources/doc/images/screenshot_database.png?raw=true) - -## Simple grid with an ORM source +## Example of a simple grid with an ORM source ```php APY\DataGridBundle\Grid\Column\DateColumn APY\DataGridBundle\Grid\Column\TimeColumn APY\DataGridBundle\Grid\Column\ArrayColumn + APY\DataGridBundle\Grid\Column\SimpleArrayColumn APY\DataGridBundle\Grid\Column\BlankColumn APY\DataGridBundle\Grid\Column\RankColumn APY\DataGridBundle\Grid\Column\JoinColumn @@ -46,6 +47,10 @@ + + + + diff --git a/Resources/config/grid.yml b/Resources/config/grid.yml new file mode 100644 index 00000000..fe1bd1da --- /dev/null +++ b/Resources/config/grid.yml @@ -0,0 +1,59 @@ +services: + # Core + apy_grid.factory: + class: APY\DataGridBundle\Grid\GridFactory + arguments: ['@service_container', '@security.authorization_checker', '@twig', '@apy_grid.registry'] + apy_grid.registry: + class: APY\DataGridBundle\Grid\GridRegistry + APY\DataGridBundle\Grid\GridFactoryInterface: + alias: 'apy_grid.factory' + APY\DataGridBundle\Grid\GridRegistryInterface: + alias: 'apy_grid.registry' + + # Types + apy_grid.type.grid: + class: APY\DataGridBundle\Grid\Type\GridType + tags: + - { name: apy_grid.type } + + # Columns + apy_grid.column.text: + class: APY\DataGridBundle\Grid\Column\TextColumn + tags: + - { name: apy_grid.column } + apy_grid.column.array: + class: APY\DataGridBundle\Grid\Column\ArrayColumn + tags: + - { name: apy_grid.column } + apy_grid.column.blank: + class: APY\DataGridBundle\Grid\Column\BlankColumn + tags: + - { name: apy_grid.column } + apy_grid.column.boolean: + class: APY\DataGridBundle\Grid\Column\BooleanColumn + tags: + - { name: apy_grid.column } + apy_grid.column.date: + class: APY\DataGridBundle\Grid\Column\DateColumn + tags: + - { name: apy_grid.column } + apy_grid.column.date_time: + class: APY\DataGridBundle\Grid\Column\DateTimeColumn + tags: + - { name: apy_grid.column } + apy_grid.column.join: + class: APY\DataGridBundle\Grid\Column\JoinColumn + tags: + - { name: apy_grid.column } + apy_grid.column.number: + class: APY\DataGridBundle\Grid\Column\NumberColumn + tags: + - { name: apy_grid.column } + apy_grid.column.rank: + class: APY\DataGridBundle\Grid\Column\RankColumn + tags: + - { name: apy_grid.column } + apy_grid.column.time: + class: APY\DataGridBundle\Grid\Column\TimeColumn + tags: + - { name: apy_grid.column } diff --git a/Resources/config/services.xml b/Resources/config/services.xml index 4e296696..3172cc01 100644 --- a/Resources/config/services.xml +++ b/Resources/config/services.xml @@ -20,8 +20,10 @@ - + + + %apy_data_grid.limits% @@ -41,26 +43,26 @@ %apy_data_grid.actions_columns_title% + + + + - + 1 - - - - - + diff --git a/Resources/doc/columns_configuration/annotations/column_annotation_property.md b/Resources/doc/columns_configuration/annotations/column_annotation_property.md index dd853bb3..51e0ac97 100644 --- a/Resources/doc/columns_configuration/annotations/column_annotation_property.md +++ b/Resources/doc/columns_configuration/annotations/column_annotation_property.md @@ -31,8 +31,7 @@ class Product * @ORM\ManyToOne(targetEntity="Category", inversedBy="products") * @ORM\JoinColumn(name="category_id", referencedColumnName="id") * - * @GRID\Column(field="category.name", title="Category Name") - * @GRID\Column(field="category.children.name", type="array", title="Category Children") + * @GRID\Column(field="category.name", title="category.name", translation_domain="categories") */ protected $category; } @@ -65,9 +64,11 @@ class Product |searchOnClick|boolean|false|true or false|Sets the possibility to perform a search on the clicked cell (filterable has to be true)| |safe|string or false|html|false
or
see [Escape filters](http://twig.sensiolabs.org/doc/filters/escape.html)|Sets the escape filter| |usePrefixTitle|boolean|true|true or false|Use the prefixTitle of the grid to render title| +|translation_domain|string|null||The translation domain that will be used for the title| **Note 1**: Every attribute has a setter and a getter method. **Note 2**: With the `values` attributes, if `type1` is found, the grid displays the value `Type 1`. **Note 3**: If operators are not visible, filtering is performed with the default operator. +**Note 4**: If you have and `id` field and want to use another field as primary, you need to set `primary=false` on the id field. ## Title translation diff --git a/Resources/doc/columns_configuration/annotations/dql_function.md b/Resources/doc/columns_configuration/annotations/dql_function.md index 0705b66d..48e539e1 100644 --- a/Resources/doc/columns_configuration/annotations/dql_function.md +++ b/Resources/doc/columns_configuration/annotations/dql_function.md @@ -103,3 +103,15 @@ class Article { `sales.name:otherFunction:string` turns into `otherFunction(_sales.name, 'string')` in DQL `other:count:distinct` turns into `count(DISTINCT _a.other)` in DQL + + +## Using GridBuilder +```php +setGroupBy('id') //important to setGroupBy otherwise the column will not aggregate + ->add('comments.id:count', 'text' ['title' => 'Number of Comments']); +``` diff --git a/Resources/doc/columns_configuration/filters/select_filter.md b/Resources/doc/columns_configuration/filters/select_filter.md old mode 100755 new mode 100644 index 774b84f2..06504c88 --- a/Resources/doc/columns_configuration/filters/select_filter.md +++ b/Resources/doc/columns_configuration/filters/select_filter.md @@ -40,7 +40,7 @@ From values: /** * @ORM\Column(type="string", length="32") * - * @GRID\Column(filter="select", selectFrom="values", values={"type1"="Type 1","type1"="Type 2"}) + * @GRID\Column(filter="select", selectFrom="values", values={"type1"="Type 1","type2"="Type 2"}) */ protected $type; ... diff --git a/Resources/doc/columns_configuration/index.md b/Resources/doc/columns_configuration/index.md new file mode 100644 index 00000000..c1b5b39e --- /dev/null +++ b/Resources/doc/columns_configuration/index.md @@ -0,0 +1,35 @@ +# Columns Configuration with Annotations + +## Annotations + +* [Source Annotation](annotations/source_annotation.md) +* [Column Annotation for a property](annotations/column_annotation_property.md) +* [Column Annotation for a class](annotations/column_annotation_class.md) +* [ORM Association Mapping](annotations/association_mapping.md) +* [DQL Functions](annotations/dql_function.md) + +## Column Types References + +* [Text Column](types/text_column.md) +* [Number Column](types/number_column.md) + * [Decimal](types/number_column.md) + * [Currency](types/number_column.md) + * [Percent](types/number_column.md) + * [Duration](types/number_column.md) + * [Scientific](types/number_column.md) + * [Spell Out](types/number_column.md) +* [Boolean Column](types/boolean_column.md) +* [DateTime Column](types/datetime_column.md) +* [Date Column](types/date_column.md) +* [_Time_](types/time_column.md) +* [Array Column](types/array_column.md) +* [Blank Column](types/blank_column.md) +* [Rank Column](types/rank_column.md) +* [Join Column](types/join_column.md) +* [Create your column](types/create_column.md) + +## Filters + +* [Input Filter](filters/input_filter.md) +* [Select Filter](filters/select_filter.md) +* [Create a filter](filters/create_filter.md) diff --git a/Resources/doc/columns_configuration/types/boolean_column.md b/Resources/doc/columns_configuration/types/boolean_column.md index 6f55bb1f..b0659288 100644 --- a/Resources/doc/columns_configuration/types/boolean_column.md +++ b/Resources/doc/columns_configuration/types/boolean_column.md @@ -15,7 +15,7 @@ See [Select filter - additionnal attributes](../filters/select_filter.md#additio ## Filter ### Valid values -1 and 0 ####### A vrifier pour les entier +1 and 0 ####### A vérifier pour les entier ### Default Operator: `eq` diff --git a/Resources/doc/columns_configuration/types/create_column.md b/Resources/doc/columns_configuration/types/create_column.md index 3c7cd717..bf8b8e77 100644 --- a/Resources/doc/columns_configuration/types/create_column.md +++ b/Resources/doc/columns_configuration/types/create_column.md @@ -112,8 +112,8 @@ class VideoColumn extends Column In your twig template: ```janjo - -{% extends 'APYDataGridBundle::blocks.html.twig' %} + +{% extends '@APYDataGrid/blocks.html.twig' %} {% block grid_column_type_video_cell %} {# Show your player with the file path store in the variable {{ value }} #} diff --git a/Resources/doc/columns_configuration/types/date_column.md b/Resources/doc/columns_configuration/types/date_column.md index bb218aa3..a0ed0a42 100644 --- a/Resources/doc/columns_configuration/types/date_column.md +++ b/Resources/doc/columns_configuration/types/date_column.md @@ -1,7 +1,7 @@ Date Column =========== -The Date Column extends the [DateTime Column](datetime_column.md) and the default fallback format is `Y-m-d`. +The Date Column extends the [DateTime Column](datetime_column.md) and the default fallback format and filter input format are `Y-m-d`. With this column, the time part of a datetime value is ignored when you filter the column. So, if you filter the column with the value `2012-04-26` or `2012-04-26 12:23:45` and with the operator `=`, the query will be `date >= '2012-04-26 0:00:00' AND date <= '2012-04-26 23:59:59'`. diff --git a/Resources/doc/columns_configuration/types/datetime_column.md b/Resources/doc/columns_configuration/types/datetime_column.md index 0db2095b..c1027b9c 100644 --- a/Resources/doc/columns_configuration/types/datetime_column.md +++ b/Resources/doc/columns_configuration/types/datetime_column.md @@ -13,7 +13,11 @@ See [Column annotation for properties](../annotations/column_annotation_property |Attribute|Type|Default value|Possible values|Description| |:--:|:--|:--|:--|:--| -|format|string|||Define this attribute if you want to force the format of the displayed value.
(e.g. "Y-m-d H:i:s")| +|format|string| | |Define this attribute if you want to force the format of the displayed value.
(e.g. "Y-m-d H:i:s")| +|timezone|string|System default timezone| |The timezone to use for rendering.
(e.g. "Europe/Paris")| +|inputFormat|string|"Y-m-d H:i:s"| |Define this attribute if you want to force the format of the filtered value.
(e.g. "Y-m-d H:i:s")| + +**Note**: If you want to filter using date input (and not datetime input), you should use the [Date Column](date_column.md) type instead and configure the display format to render the time (e.g. "Y-m-d H:i:s"). ## Filter #### Valid values @@ -40,4 +44,4 @@ Wrong values are ignored. |btw|Between exclusive| |btwe|Between inclusive| |isNull|Is not defined| -|isNotNull|Is defined| \ No newline at end of file +|isNotNull|Is defined| diff --git a/Resources/doc/configuration.md b/Resources/doc/configuration.md new file mode 100644 index 00000000..9f914ba6 --- /dev/null +++ b/Resources/doc/configuration.md @@ -0,0 +1,21 @@ +# APYDataGrid Configuration Reference + +All available configuration options are listed below with their default values. + +```yaml +apy_data_grid: + limits: [20, 50, 100] + persistence: false + theme: '@APYDataGrid/blocks.html.twig' + no_data_message: "No data" + no_result_message: "No result" + actions_columns_size: -1 + actions_columns_title: "Actions" + actions_columns_separator: "
" + pagerfanta: + enable: false + view_class: "Pagerfanta\\View\\DefaultView" + options: + prev_message: "«" + next_message: "»" +``` diff --git a/Resources/doc/export/create_export.md b/Resources/doc/export/create_export.md index 13cfaec5..40f3d114 100644 --- a/Resources/doc/export/create_export.md +++ b/Resources/doc/export/create_export.md @@ -159,7 +159,7 @@ We'll try to create a CSV export. **Note**: These functions return the array of titles only if titles are visible. - #### Export::getFlatGridData and Export::getRawFlatGridData + #### Export::getFlatGridData and Export::getFlatRawGridData These functions return an flat array of rows. If titles are visible the first index of the array is the array of titles. @@ -223,7 +223,7 @@ We'll try to create a CSV export. } ``` - Voil, you can export your grid in a csv file. + Voilà, you can export your grid in a csv file. 7. **Additional parameters** diff --git a/Resources/doc/export/index.md b/Resources/doc/export/index.md new file mode 100644 index 00000000..33409648 --- /dev/null +++ b/Resources/doc/export/index.md @@ -0,0 +1,35 @@ +# Export + +APYDataGrid bundle provides different ways for export your datas. This bundle proposes native exports such as a CSV or a JSON export and library-dependent exports such as Excel and PDF exports but everything is made that it is really easy to create your own export. + +> Note: An export don't export mass action and row actions columns. + +## Native Exports + +* [CSV Export](native_exports/CSV_export.md) +* [DSV Export](native_exports/DSV_export.md) +* [Excel Export](native_exports/Excel_export.md) +* [JSON Export](native_exports/JSON_export.md) +* [SCVS Export](native_exports/SCVS_export.md) +* [TSV Export](native_exports/TSV_export.md) +* [XML Export](native_exports/XML_export.md) + +## External Library Exports + +### With PHPExcel + +Add the following package to your composer.json file: + +```bash +$ composer require phpoffice/phpexcel "dev-master" +``` + +* [PHPExcel Excel 2007 Export](library-dependent_exports/PHPExcel/PHPExcel_excel2007_export.md) +* [PHPExcel Excel 2003 Export](library-dependent_exports/PHPExcel/PHPExcel_excel2003_export.md) +* [PHPExcel Excel 5 (97-2003) Export](library-dependent_exports/PHPExcel/PHPExcel_excel5_export.md) +* [PHPExcel Simple HTML Export](library-dependent_exports/PHPExcel/PHPExcel_HTML_export.md) +* [PHPExcel simple PDF export](library-dependent_exports/PHPExcel/PHPExcel_PDF_export.md) + +## Cook Book + +* [How to create your custom export](create_export.md) \ No newline at end of file diff --git a/Resources/doc/export/library-dependent_exports/PHPExcel/PHPExcel_installation.md b/Resources/doc/export/library-dependent_exports/PHPExcel/PHPExcel_installation.md deleted file mode 100644 index 41101b06..00000000 --- a/Resources/doc/export/library-dependent_exports/PHPExcel/PHPExcel_installation.md +++ /dev/null @@ -1,16 +0,0 @@ -PHPExcel installation -===================== - -Add the foolowing package to your composer.json file: -```js -{ - "require": { - "phpoffice/phpexcel": "dev-master" - } -} -``` - -Execute this command: -```bash -$ php composer.phar update phpoffice/phpexcel -``` diff --git a/Resources/doc/features.md b/Resources/doc/features.md new file mode 100644 index 00000000..e2e8c114 --- /dev/null +++ b/Resources/doc/features.md @@ -0,0 +1,40 @@ +# APYDataGrid Bundle Features + +- Supports Entity (ORM), Document (ODM) and Vector (Array) sources +- Sortable and Filterable with operators (Comparison operators, range, starts/ends with, (not) contains, is (not) defined, regex) +- Auto-typing columns (Text, Number, Boolean, Array, DateTime, Date, ...) +- Locale support for DateTime, Date and Number columns (Decimal, Currency, Percent, Duration, Scientific, Spell out) +- Input, Select, checkbox and radio button filters filled with the data of the grid or an array of values +- Export (CSV, Excel, _PDF_, XML, JSON, HTML, ...) +- Mass actions +- Row actions +- Supports mapped fields with Entity source +- Securing the columns, actions and export with security roles +- Annotations and PHP configuration +- External filters box +- Ajax loading +- Pagination (You can also use Pagerfanta) +- Column width and column align +- Prefix translated titles +- Grid manager for multi-grid on the same page +- Groups configuration for ORM and ODM sources +- Easy templates overriding (twig) +- Custom columns and filters creation + +## Screenshot + +Full example with this [CSS style file](https://github.com/APY/APYDataGridBundle/blob/master/Resources/doc/grid_configuration/working_example.css): + +![test](https://github.com/APY/APYDataGridBundle/blob/master/Resources/doc/images/screenshot_full.png?raw=true) + +Simple example with the external filter box in english: + +![test](https://github.com/APY/APYDataGridBundle/blob/master/Resources/doc/images/screenshot_en.png?raw=true) + +Same example in french: + +![test](https://github.com/APY/APYDataGridBundle/blob/master/Resources/doc/images/screenshot_fr.png?raw=true) + +Data used in these screenshots (this is a phpMyAdmin screenshot): + +![test](https://github.com/APY/APYDataGridBundle/blob/master/Resources/doc/images/screenshot_database.png?raw=true) \ No newline at end of file diff --git a/Resources/doc/grid.md b/Resources/doc/grid.md new file mode 100644 index 00000000..948ec58b --- /dev/null +++ b/Resources/doc/grid.md @@ -0,0 +1,154 @@ +DataGrid +======== + +An entity +--------- +```php +class Product +{ + /** + * @var int + */ + protected $id; + + /** + * @var string + */ + protected $name; + + /** + * @var DateTime + */ + protected $createdAt; + + /** + * @var string + */ + protected $status; + + // Getters and setters ... +} +``` +Creating the grid from the grid builder +--------------------------------------- + +Creating a grid requires relatively little code because the grid objects are built with a "grid builder". +The grid builder's purpose is to allow you to write simple grid, and have it do all the heavy-lifting of actually building the grid. + +```php +class ProductController extends Controller +{ + + public function listAction(Request $request) + { + // Creates the builder + $gridBuilder = $this->createGridBuilder(new Entity('MyProjectBundle:Product'), [ + 'persistence' => true, + 'route' => 'product_list', + 'filterable' => false, + 'sortable' => false, + 'max_per_page' => 20, + ]); + + // Creates columns + $grid = $gridBuilder + ->add('id', 'number', [ + 'title' => '#', + 'primary' => 'true', + ]) + ->add('name', 'text') + ->add('created_at', 'datetime', [ + 'field' => 'createdAt', + ]) + ->add('status', 'text') + ->getGrid(); + + // Handles filters, sorts, exports, ... + $grid->handleRequest($request); + + // Renders the grid + return $this->render('MyProjectBundle:Product:list', ['grid' => $grid]); + } + + /** + * @return GridBuilder + */ + public function createGridBuilder(Source $source = null, array $options = []) + { + return $this->container->get('apy_grid.factory')->createBuilder('grid', $source, $options); + } +} +``` + +Creating a grid from a type +--------------------------- + +A better practice is to build the grid in a separate, standalone PHP class, which can then be reused anywhere in your application. +Create a new class that will house the logic for building the product grid: + +```php +class ProductListType extends GridType +{ + public function buildGrid(GridBuilder $builder, array $options = []) + { + parent::buildGrid($builder, $options); + + $builder + ->add('id', 'number', [ + 'title' => '#', + 'primary' => 'true', + ]) + ->add('name', 'text') + ->add('created_at', 'datetime', [ + 'field' => 'createdAt', + ]) + ->add('status', 'text'); + } + + public function configureOptions(OptionsResolver $resolver) + { + parent::configureOptions($resolver); + + $resolver->setDefaults([ + 'source' => new Entity('MyProjectBundle:Product'), + 'persistence' => true, + 'route' => 'product_list', + 'filterable' => false, + 'sortable' => false, + 'max_per_page' => 20, + ]); + } + + public function getName() + { + return 'product_list'; + } +} +``` + +It can be used to quickly build a grid object in the controller: + +```php +class ProductController extends Controller +{ + + public function listAction(Request $request) + { + // Creates the grid from the type + $grid = $this->createGrid(new ProductListType()); + + // Handles filters, sorts, exports, ... + $grid->handleRequest($request); + + return $this->render('MyProjectBundle:Product:list', ['grid' => $grid]); + } + + /** + * @return Grid + */ + public function createGrid($type, Source $source = null, array $options = []) + { + $this->container->get('apy_grid.factory')->create($type, $source, $options); + } +} +``` diff --git a/Resources/doc/grid_configuration/add_actions_column.md b/Resources/doc/grid_configuration/add_actions_column.md index 70e4e228..587991de 100644 --- a/Resources/doc/grid_configuration/add_actions_column.md +++ b/Resources/doc/grid_configuration/add_actions_column.md @@ -25,7 +25,7 @@ $grid->addRowAction($rowAction1); // OR add a second row action directly to a new action column $rowAction2 = new RowAction('Edit', 'route_to_edit'); -$actionsColumn2 = new ActionsColumn($column, $title, array(rowAction2), $separator); +$actionsColumn2 = new ActionsColumn($column, $title, array($rowAction2), $separator); $grid->addColumn($actionsColumn2, $position2); ... ``` @@ -70,7 +70,7 @@ $grid->addRowAction($rowAction1); // OR add a second row action directly to a new action column $rowAction2 = new RowAction('Edit', 'route_to_edit'); -$actionsColumn2 = new ActionsColumn('info_column_2', 'Actions 3', array(rowAction2)); +$actionsColumn2 = new ActionsColumn('info_column_2', 'Actions 3', array($rowAction2)); $grid->addColumn($actionsColumn2, 2); ... ``` diff --git a/Resources/doc/grid_configuration/index.md b/Resources/doc/grid_configuration/index.md new file mode 100644 index 00000000..fffcaf59 --- /dev/null +++ b/Resources/doc/grid_configuration/index.md @@ -0,0 +1,102 @@ +# Grid Configuration with PHP + +#### [Set the identifier of the grid](set_grid_identifier.md) +You can set the identifier of a grid to manage easily the grid with css and javascript for instance. + +#### [Set the persistence of the grid](set_grid_persistence.md) +By default, filters, page and order are reset when you quit the page where your grid is. If you set to true the persistence, its parameters are kept until you close your web browser or you kill the session cookie yourself. + +#### [Pagination](set_limits.md) +Define the selector of the number of items per page + +#### [Set max results](set_max_results.md) +Set max results. + +#### [Add column](add_column.md) +You can add a column to the grid. You can fill it with the row manipulator, in your template or tell the grid what field the column will be mapped. + +#### [Add row action](add_row_action.md) +A row action is an action performed on the current row. It's represented by a route to a controller with the identifier of the row. + +#### [Add multiple row actions columns](add_actions_column.md) +You can create other columns of row actions and choose the position of these ones. + +#### [Add mass action](add_mass_action.md) +A mass action is like a row action but over many lines at the same time. + +#### [Add a native delete mass action](add_delete_mass_action.md) +This mass action calls the delete method of the source. + +#### [Add export](add_export.md) + + +#### [Manipulate rows data](manipulate_rows_data.md) +You can set a callback to manipulate the row of the grid. + +#### [Manipulate column render cell](manipulate_column_render_cell.md) +You can set a callback to manipulate the render of a cell. + +#### [Manipulate the source query](manipulate_query.md) +The Entity Source provides two ways of manipulating the query which is used for generating the grid. + +#### [Manipulate the count query (source Entity only)](manipulate_count_query.md) +The grid requires the total number of results (COUNT (...)). The grid clones the source QueryBuilder and wraps it with a COUNT DISTINCT clause. + +#### [Manipulate columns](manipulate_column.md) +You can manipulate the behavior of a column. + +#### [Manipulate row action rendering](manipulate_row_action_rendering.md) +You can set a callback to manipulate the rendering of an action. + +#### [Hide or show columns](hide_show_columns.md) +These functions are helpers to manipulate columns. + +#### [Set columns order](set_columns_order.md) +You can already define the order of the columns with the columns option of the Source annotation. + +#### [Set a default page](set_default_page.md) +You can define a default page. This page will be used on each new session of the grid. + +#### [Set a default order](set_default_order.md) +You can define a default order. This order will be used on each new session of the grid. + +#### [Set a default items per page](set_default_limit.md) +You can define a default limit. This limit will be used on each new session of the grid. + +#### [Set default filters](set_default_filters.md) +You can define default filters. These values will be used on each new session of the grid. + +#### [Set permanent filters](set_permanent_filters.md) +You can define permanent filters. These values will be used every time and the filter part will be disable for columns which have a permanent filter. + +#### [Set the no data message](set_no_data_message.md) +When you render a grid with no data in the source, the grid isn't displayed and a no data message is displayed. + +#### [Set the no result message](set_no_result_message.md) +When you render a grid with no result after a filtering, a no result message is displayed in a unique row. + +#### [Always show the grid](always_show_grid.md) +When you render a grid with no data in the source, the grid isn't displayed and a no data message is displayed. + +#### [Set a title prefix](set_prefix_titles.md) +You can define a prefix title for all columns of the grid. + +#### [Set the size of the actions colum](set_size_actions_column.md) +Set the size of the actions column. + +#### [Set the title of the actions colum](set_title_actions_column.md) +Set the title of the actions column. + +#### [Set data to avoid calling the database](set_data.md) +You can use fetched data to avoid unnecessary queries. + +#### [Grid Response helper](grid_response.md) +The getGridResponse method is an helper which manage the redirection, export and the rendering of the grid. + +#### [Multi Grid manager](multi_grid_manager.md) +Handle multiple grids on the same page. + +#### [Set the route of the grid](set_grid_route.md) +The route of a grid is automatically retrieved from the request. But when you render a controller which contains a grid from twig, the route cannot be retrieved so you have to define it. + +#### [Working Example](working_example.md) diff --git a/Resources/doc/grid_configuration/manipulate_count_query.md b/Resources/doc/grid_configuration/manipulate_count_query.md new file mode 100644 index 00000000..4927af84 --- /dev/null +++ b/Resources/doc/grid_configuration/manipulate_count_query.md @@ -0,0 +1,45 @@ +Manipulating the count query builder +============================ + +The grid requires the total number of results (COUNT (...)). The grid clones the source QueryBuilder and wraps it with a COUNT DISTINCT clause. +If you use a lot of aggregation in the source queryBuilder you may encounter performance problems. +You can manipulate the query before it is processed to remove useless fields for the COUNT clause. + +## 1. Using a callback + +```php +manipulateCountQuery($callback); + +$grid->setSource($source); +... +``` + +### Method Source::manipulateCountQuery parameters + +|parameter|Type|Default value|Description| +|:--:|:--|:--|:--|:--| +|callback|[\Closure](http://php.net/manual/en/functions.anonymous.php) or [callable](http://php.net/manual/en/language.types.callable.php)|null|Callback to manipulate the query. Null means no callback.| + +### Callback parameters + +|parameter|Type|Description| +|:--:|:--|:--|:--|:--| +|queryBuilder|instance of QueryBuilder|The QueryBuilder instance before its execution (clone of the source QueryBuilder)| + +### Examples + +```php +manipulateCountQuery( + function ($queryBuilder) + { + $queryBuilder->resetDQLPart('select'); + } +); + +$grid->setSource($source); +... +``` diff --git a/Resources/doc/grid_configuration/manipulate_row_action_rendering.md b/Resources/doc/grid_configuration/manipulate_row_action_rendering.md index 0365e973..730745aa 100644 --- a/Resources/doc/grid_configuration/manipulate_row_action_rendering.md +++ b/Resources/doc/grid_configuration/manipulate_row_action_rendering.md @@ -28,6 +28,11 @@ $grid->addRowAction($rowAction); |action|instance of RowAction|The action| |row|instance of Row|The current row| +## Action disabling + +The action can be disabled using the manipulate render callback. See the example. +If the action is disabled, only its title is displayed, with all additional attributes used. + ## Example ```php @@ -44,6 +49,10 @@ $rowAction->manipulateRender( return null; } + if ($row->getField('enabled') == false) { + $action->setEnabled(false); + } + return $action; } ); diff --git a/Resources/doc/grid_configuration/multi_grid_manager.md b/Resources/doc/grid_configuration/multi_grid_manager.md index ea81421a..6225f6d3 100644 --- a/Resources/doc/grid_configuration/multi_grid_manager.md +++ b/Resources/doc/grid_configuration/multi_grid_manager.md @@ -69,11 +69,6 @@ $grid2->setSource($source2); if ($gridManager->isReadyForRedirect()) { - if ($gridManager->isReadyForExport()) - { - return $gridManager->getExportResponse(); - } - return new RedirectResponse($gridManager->getRouteUrl()); } else diff --git a/Resources/doc/grid_configuration/set_grid_persistence.md b/Resources/doc/grid_configuration/set_grid_persistence.md index 0a407426..17cb5187 100644 --- a/Resources/doc/grid_configuration/set_grid_persistence.md +++ b/Resources/doc/grid_configuration/set_grid_persistence.md @@ -3,7 +3,7 @@ Set the persistence of the grid By default, filters, page and order are reset when you quit the page where your grid is. -If you set to true the persistence, its parameters are kept until you close your web browser or you kill yourself the cookie of the session. +If you set to true the persistence, its parameters are kept until you close your web browser or you kill the session cookie yourself. But don't forget to define an different identifier of your grids else your sessions will be reset by another grid with the same identifier. ## Usage @@ -37,4 +37,4 @@ $grid->setPersistence(true); ```yml apy_data_grid: persistence: true -``` \ No newline at end of file +``` diff --git a/Resources/doc/index.md b/Resources/doc/index.md new file mode 100644 index 00000000..b418bf4c --- /dev/null +++ b/Resources/doc/index.md @@ -0,0 +1,128 @@ +# APY DataGrid Bundle + +APYDataGridBundle is a Symfony bundle for create grids for list your Entity (ORM), Document (ODM) and Vector (Array) sources. [APYDataGridBundle](https://github.com/APY/APYDataGridBundle) was initiated by **Stanislav Turza (Sorien)** and inspired by **Zfdatagrid and Magento Grid**. + +> You can see [CHANGELOG](CHANGELOG.md) and [UPGRADE 2.0](UPGRADE-2.0.md). + +## Prerequisites + +This version of the bundle requires Symfony 3.0+. + +### Translations + +If you wish to use default texts provided in this bundle, you have to make sure you have translator enabled in your config. + +```yaml +# app/config/config.yml +framework: + translator: ~ +``` + +For more information about translations, check [Symfony documentation](https://symfony.com/doc/current/book/translation.html). + +## Installation + +### Step 1 : Download APYDataGridBundle using composer + +Require the bundle with composer : + +```bash +$ composer require apy/datagrid-bundle +``` + +Composer will install the bundle to your project's *vendor/apy/datagrid-bundle* directory. + +### Step 2 : Enable the bundle + +Enable the bundle in the kernel : + +```php +// app/AppKernel.php + +public function registerBundles() +{ + $bundles = array( + // ... + new APY\DataGridBundle\APYDataGridBundle(), + // ... + ); +} +``` + +### Step 3 : Quick start with APYDataGridBundle + +#### Create simple grid with an ORM source in your controller + +```php +get('grid'); + + // Attach the source to the grid + $grid->setSource($source); + + // Return the response of the grid to the template + return $grid->getGridResponse('MyProjectMyBundle:myGrid.html.twig'); + } +} +``` + +#### Create simple configuration of the grid in the entity + +```php + +{{ grid(grid) }} +``` + +> Don't forget to clean your cache ! + +### Next Steps + +Now you have completed the basic installation and configuration of the APYDataGridBundle, you are ready to learn about more advanced features and usages of the bundle. + +The following documents are available : + +* [Getting Started With APYDataGridBundle](getting_started.md) +* [Setting the Grid Source](source/index.md) +* [Display the Grid (Twig template)](template/index.md) +* [Columns Configuration with Annotations](columns_configuration/index.md) +* [Grid Configuration with PHP](grid_configuration/index.md) +* [Export](export/index.md) +* [APYDataGridBundle Configuration Reference](configuration.md) diff --git a/Resources/doc/installation.md b/Resources/doc/installation.md deleted file mode 100644 index 869027c7..00000000 --- a/Resources/doc/installation.md +++ /dev/null @@ -1,25 +0,0 @@ -Installation -============ - -### Step 1: Download DataGridBundle using Composer - -```bash -$ composer require apy/datagrid-bundle -``` - -### Step 2: Enable the bundle - -Finally, enable the bundle in the kernel: - -``` php - if you want your data mapped on these columns. +**Note:** + +* Attributes `id` and `field` are required. The `id` is the identifier of your column and `field` is the name of your field to map with you data. +* Columns are not sourcable and mapped with id by default, you have to define source=true and field= if you want your data mapped on these columns. ## Set a primary field diff --git a/Resources/doc/summary.md b/Resources/doc/summary.md index 86e0f445..cc681a3d 100644 --- a/Resources/doc/summary.md +++ b/Resources/doc/summary.md @@ -3,7 +3,7 @@ SUMMARY 1. [Introduction](https://github.com/Abhoryo/APYDataGridBundle/blob/master/README.md) -1. [Installation](installation.md) +1. [Installation](index.md) 1. [Getting Started with APYDataGridBundle](getting_started.md) @@ -65,6 +65,7 @@ SUMMARY 1. [Manipulate rows data](grid_configuration/manipulate_rows_data.md) 1. [Manipulate column render cell](grid_configuration/manipulate_column_render_cell.md) 1. [Manipulate the source query](grid_configuration/manipulate_query.md) + 1. [Manipulate the count query (source Entity only)](grid_configuration/manipulate_count_query.md) 1. [Manipulate columns](grid_configuration/manipulate_column.md) 1. [Manipulate row action rendering](grid_configuration/manipulate_row_action_rendering.md) 1. [Hide or show columns](grid_configuration/hide_show_columns.md) diff --git a/Resources/doc/template/cell_rendering.md b/Resources/doc/template/cell_rendering.md index 11e12db2..982102dc 100644 --- a/Resources/doc/template/cell_rendering.md +++ b/Resources/doc/template/cell_rendering.md @@ -37,12 +37,12 @@ However this naming convention is not advised as it is ambiguous. It is only sup #### Use icons for boolean columns with passed additional parameters ```janjo -grid(grid, 'MyProjectMyBundle::my_grid_template.html.twig', '', {'imgDir': 'img/'}) +grid(grid, 'my_grid_template.html.twig', '', {'imgDir': 'img/'}) ``` ```janjo - -{% extends 'APYDataGridBundle::blocks.html.twig' %} + +{% extends '@APYDataGrid/blocks.html.twig' %} {% block grid_column_type_boolean_cell %} {{ value }} @@ -52,12 +52,12 @@ grid(grid, 'MyProjectMyBundle::my_grid_template.html.twig', '', {'imgDir': 'img/ #### Use the SearchOnclick functionality with the previous block ```janjo -grid(grid, 'MyProjectMyBundle::my_grid_template.html.twig', '', {'imgDir': 'img/'}) +grid(grid, 'my_grid_template.html.twig', '', {'imgDir': 'img/'}) ``` ```janjo - -{% extends 'APYDataGridBundle::blocks.html.twig' %} + +{% extends '@APYDataGrid/blocks.html.twig' %} {% block grid_column_type_boolean_cell %} {% set value = '~value~' %} diff --git a/Resources/doc/template/render_the_grid.md b/Resources/doc/template/index.md similarity index 62% rename from Resources/doc/template/render_the_grid.md rename to Resources/doc/template/index.md index 69466a87..8069a350 100644 --- a/Resources/doc/template/render_the_grid.md +++ b/Resources/doc/template/index.md @@ -1,5 +1,4 @@ -Render the grid -================ +# Display the Grid (Twig template) ## Usage @@ -8,24 +7,30 @@ Twig variable ```grid```. ```php get('grid'); - -$grid->setSource($source); - -return $grid->getGridResponse('MyProjectMyBundle::my_grid.html.twig'); -... +class DefaultController extends Controller +{ + public function myGridAction() + { + // [...] + $grid = $this->get('grid'); + + $grid->setSource($source); + + return $grid->getGridResponse('MyProjectMyBundle::my_grid.html.twig'); + } + // [...] +} ``` -And the template +And the Twig template -```janjo +```djanjo {{ grid(grid, theme, id, params) }} ``` -## grid function parameters +## Grid Function Parameters Reference |parameter|Type|Default value|Description| |:--:|:--|:--|:--|:--| @@ -34,16 +39,7 @@ And the template |id|string|_none_|Set the identifier of the grid.| |params|array|array()|Additional parameters passed to each block.| -## Example - -```janjo - - -{{ grid(grid) }} -... -``` - -## Override the getGridResponse function +## Overriding the getGridResponse function See [Grid Response helper](../grid_configuration/grid_response.md) for a detailed outline of ```getGridResponse```. @@ -66,3 +62,12 @@ if ($grid->isReadyForRedirect()) { ``` **Note:** GridResponse parameters are useless in this case and exports are managed directly in the getGridResponse function. + +## Learn more about advanced features and usages + +* [Display an ajax grid](render_an_ajax_grid.md) +* [Cell rendering](cell_rendering.md) +* [Filter rendering](filter_rendering.md) +* [Overriding internal blocks](overriding_internal_blocks.md) +* [Display an external filters box](render_external_filters.md) +* [Display a pagerfanta pager](render_pagerfanta_pager.md) \ No newline at end of file diff --git a/Resources/doc/template/overriding_internal_blocks.md b/Resources/doc/template/overriding_internal_blocks.md index f6020330..2ff70dae 100644 --- a/Resources/doc/template/overriding_internal_blocks.md +++ b/Resources/doc/template/overriding_internal_blocks.md @@ -6,8 +6,8 @@ Overriding internal blocks If you want to override blocks of the grid you can use a extended template of the grid template. ```twig - -{% extends 'APYDataGridBundle::blocks.html.twig' %} + +{% extends '@APYDataGrid/blocks.html.twig' %} {% block grid_pager %}{% endblock %} @@ -19,25 +19,25 @@ If you want to override blocks of the grid you can use a extended template of th This template can then be passed to the grid twig function as second parameter ```twig - -{{ grid(data, 'MyProjectMyBundle::my_page_grid.html.twig') }} + +{{ grid(data, 'my_page_grid.html.twig') }} ``` You can also apply the template globaly to your whole app by specifing it in your config. ```yaml apy_data_grid: - theme: 'MyProjectMyBundle::my_page_grid.html.twig' + theme: 'my_page_grid.html.twig' ``` ## _self template If you want to override blocks inside the current template you can use the `_self` parameter in grid template definition. -Current template will automatically extended from the base block template (APYDataGridBundle::blocks.html.twig) +Current template will automatically extended from the base block template (@APYDataGrid/blocks.html.twig) ```twig - -{% extends 'MyProjectMyBundle::layout.html.twig' %} + +{% extends 'layout.html.twig' %} {% block content %} {{ grid(grid, _self) }} diff --git a/Resources/doc/template/render_an_ajax_grid.md b/Resources/doc/template/render_an_ajax_grid.md index 9dd0115f..b312cef6 100644 --- a/Resources/doc/template/render_an_ajax_grid.md +++ b/Resources/doc/template/render_an_ajax_grid.md @@ -2,21 +2,21 @@ Render an ajax grid =================== You can load the grid with ajax interactions. -Simply call or extend the template `APYDataGridBundle::blocks_js.jquery.html.twig` instead of `APYDataGridBundle::blocks.html.twig`. +Simply call or extend the template `@APYDataGrid/blocks_js.jquery.html.twig` instead of `@APYDataGrid/blocks.html.twig`. This template only works with the jQuery Javascript Framework but you can change it to manage this feature with your own Javascript Framework. ## Usage -Before : `{{ grid(data, 'APYDataGridBundle::blocks.html.twig') }}` -After: `{{ grid(data, 'APYDataGridBundle::blocks_js.jquery.html.twig') }}` +Before : `{{ grid(data, '@APYDataGrid/blocks.html.twig') }}` +After: `{{ grid(data, '@APYDataGrid/blocks_js.jquery.html.twig') }}` **Note**: The grid_search twig function doesn't need to extend this same template because its script are already included in the grid template. #### Example ```django -{{ grid_search(data, 'APYDataGridBundle::blocks.html.twig') }} +{{ grid_search(data, '@APYDataGrid/blocks.html.twig') }} -{{ grid(data, 'APYDataGridBundle::blocks_js.jquery.html.twig') }} +{{ grid(data, '@APYDataGrid/blocks_js.jquery.html.twig') }} ``` \ No newline at end of file diff --git a/Resources/doc/template/render_external_filters.md b/Resources/doc/template/render_external_filters.md index 1aef918a..799b5089 100644 --- a/Resources/doc/template/render_external_filters.md +++ b/Resources/doc/template/render_external_filters.md @@ -10,14 +10,14 @@ Pass the $grid object to the view and call your grid render in your template. ... $grid = $this->get('grid'); -return $grid->getGridResponse('MyProjectMyBundle::my_grid.html.twig'); +return $grid->getGridResponse('grid.html.twig'); ... ``` And the template ```janjo - + {{ grid_search(grid, theme, id, params) }} ... @@ -29,7 +29,7 @@ And the template |parameter|Type|Default value|Description| |:--:|:--|:--|:--|:--| |grid|APY/DataGridBundle/Grid/Grid||The grid object| -|theme|string|APYDataGridBundle::blocks.html.twig|Template used to render the filters blocks| +|theme|string|@APYDataGrid/blocks.html.twig|Template used to render the filters blocks| |id|string|_none_|Set the identifier of the grid.| |params|array|array()|Additional parameters passed to each block.| @@ -38,7 +38,7 @@ And the template #### Example ```janjo - + {{ grid_search(grid) }} @@ -52,9 +52,9 @@ And the template If you don't want to show the filter in the grid columns, you can disable the grid_filters blocks with an external template. -`{{ grid(grid, 'MyProjectMyBundle::grid.html.twig') }}` +`{{ grid(grid, 'grid.html.twig') }}` -And in your MyProjectMyBundle::grid.html.twig template +And in your `grid.html.twig` template ```janjo {% block grid_filters %}{% endblock %} diff --git a/Resources/translations/messages.cs.xliff b/Resources/translations/messages.cs.xliff index bb794633..a81e39cc 100644 --- a/Resources/translations/messages.cs.xliff +++ b/Resources/translations/messages.cs.xliff @@ -2,6 +2,54 @@ + + Page + strana + + + , Display + , zobraz + + + of %count% + z %count% + + + Items per page + položek na stranu + + + Select visible + Označ viditelné + + + Select all + Označ všechno + + + Deselect visible + Odznač viditelné + + + Deselect all + Odznač všechno + + + Action + Akce + + + Submit Action + Odeslat + + + From: + Od: + + + To: + Do: + eq Rovná se @@ -64,8 +112,52 @@ %count% Results, - %count% Výsledek, |%count% Výsledků, + %count% výsledek, |%count% výsledky, |%count% výsledků, + + + Search + Hledat + + + Reset + Resetovat + + + Order by + Řadit dle + + + slike + Obsahuje + + + nslike + Neobsahuje + + + rslike + Začíná na + + + lslike + Končí na + + + No data + Žádná data + + + No result + Žádné výsledky + + + Selected _s_ rows + Označeno _s_ řádků + + + Actions + Akce - \ No newline at end of file + diff --git a/Resources/translations/messages.en.xliff b/Resources/translations/messages.en.xliff index 11ba3946..33ab8486 100644 --- a/Resources/translations/messages.en.xliff +++ b/Resources/translations/messages.en.xliff @@ -2,6 +2,46 @@ + + Page + Page + + + , Display + , Display + + + of %count% + of %count% + + + Items per page + Items per page + + + Select visible + Select visible + + + Select all + Select all + + + Deselect visible + Deselect visible + + + Deselect all + Deselect all + + + Action + Action + + + Submit Action + Submit Action + eq Equals @@ -66,6 +106,18 @@ %count% Results, %count% Result, |%count% Results, + + Search + Search + + + Reset + Reset + + + Order by + Order by + @@ -138,6 +190,22 @@ lslike Ends with + + No data + No data + + + No result + No result + + + Selected _s_ rows + Selected _s_ rows + + + Actions + Actions + - \ No newline at end of file + diff --git a/Resources/translations/messages.pl.xliff b/Resources/translations/messages.pl.xliff index 2be9d822..0c3ff253 100644 --- a/Resources/translations/messages.pl.xliff +++ b/Resources/translations/messages.pl.xliff @@ -64,7 +64,7 @@ %count% Results, - %count% Wynik, |%count% Wyników, + {0} %count% Wyników|{1} %count% Wynik|]1,Inf[ %count% Wyników, diff --git a/Resources/views/blocks.html.twig b/Resources/views/blocks.html.twig index e9230061..466ceac6 100644 --- a/Resources/views/blocks.html.twig +++ b/Resources/views/blocks.html.twig @@ -1,7 +1,7 @@ {# ------------------------------------------------------ grid ------------------------------------------------------ #} {% block grid %}
-{% if grid.totalCount > 0 or grid.isFiltered or grid.noDataMessage is sameas(false) %} +{% if grid.totalCount > 0 or grid.isFiltered or grid.noDataMessage is same as (false) %}
{% if grid.massActions|length > 0 %} @@ -43,7 +43,7 @@ {% block grid_no_data %}

{{ grid.noDataMessage|default('No data')|trans|raw }}

{% endblock grid_no_data %} {# --------------------------------------------------- grid_no_result ------------------------------------------------- #} {% block grid_no_result %} -{% spaceless %} +{% apply spaceless %} {% set nbColumns = 0 %} {% for column in grid.columns %} {% if column.visible(grid.isReadyForExport) %} @@ -53,38 +53,48 @@ {{ grid.noResultMessage|default('No result')|trans|raw }} -{% endspaceless %} +{% endapply %} {% endblock grid_no_result %} {# --------------------------------------------------- grid_titles -------------------------------------------------- #} {% block grid_titles %} {% for column in grid.columns %} + {% set translation_domain = column.translationDomain %} {% if column.visible(grid.isReadyForExport) %} -1) %} style="width:{{ column.size }}px;"{% endif %}> - {%- spaceless %} + {% apply spaceless %} {% if column.type == 'massaction' %} - {% else %} + {% else %} + {% set columnTitle = column.title %} + {% if column.usePrefixTitle == true %} - {% set columnTitle = grid.prefixTitle ~ column.title ~ '__abbr' %} - {% if columnTitle|trans == columnTitle %} - {% set columnTitle = grid.prefixTitle ~ column.title %} + {% if translation_domain is same as(false) %} + {%- set columnTitle = grid.prefixTitle ~ column.title -%} + {% else %} + {%- set columnTitle = grid.prefixTitle ~ columnTitle ~ '__abbr' -%} + {% if columnTitle|trans({}, translation_domain) == columnTitle %} + {%- set columnTitle = grid.prefixTitle ~ column.title -%} + {% endif %} {% endif %} - {% else %} - {% set columnTitle = column.title %} {% endif %} + + {% if translation_domain is not same as(false) %} + {%- set columnTitle = columnTitle|trans({}, translation_domain) -%} + {% endif %} + {% if (column.sortable) %} -
{{ columnTitle|trans }} + {{ columnTitle }} {% if column.order == 'asc' %}
{% elseif column.order == 'desc' %}
{% endif %} {% else %} - {{ columnTitle|trans }} + {{ columnTitle }} {% endif %} {% endif %} - {% endspaceless -%} + {% endapply -%} {% endif %} {% endfor %} @@ -124,7 +134,7 @@ {% block grid_rows %} {% for row in grid.rows %} {% set last_row = loop.last %} - {% spaceless %} + {% apply spaceless %} {% set gridColumns %} {% for column in grid.columns %} {% if column.visible(grid.isReadyForExport) %} @@ -134,7 +144,7 @@ {% endset %} {{ gridColumns }} - {% endspaceless %} + {% endapply %} {% else %} {{ grid_no_result(grid) }} @@ -154,16 +164,16 @@ {% endblock grid_pager %} {# ---------------------------------------------------- grid_pager_totalcount -------------------------------------------------- #} {% block grid_pager_totalcount %} -{{ '%count% Results, ' | transchoice(grid.totalCount, {'%count%': grid.totalCount}) }} +{{ '%count% Results, ' | trans({'%count%': grid.totalCount}) }} {% endblock grid_pager_totalcount %} {# ---------------------------------------------------- grid_pager_selectpage -------------------------------------------------- #} {% block grid_pager_selectpage %} {{ 'Page'|trans }} -{% spaceless %} +{% apply spaceless %} = grid.pageCount-1 %}disabled="disabled"{% endif %} onclick="return {{ grid.hash }}_nextPage();"/> {{ 'of %count%'|trans({ '%count%' : grid.pageCount }) }} -{% endspaceless %} +{% endapply %} {% endblock grid_pager_selectpage %} {# ---------------------------------------------------- grid_pager_results_perpage -------------------------------------------------- #} {% block grid_pager_results_perpage %} @@ -184,7 +194,7 @@ {{ 'Deselect all'|trans }} - {% spaceless %} + {% apply spaceless %}
{{ 'Action'|trans }} @@ -196,13 +206,13 @@
- {% endspaceless %} + {% endapply %}
{% endblock grid_actions %} {# --------------------------------------------------- grid_exports ------------------------------------------------- #} {% block grid_exports %}
- {% spaceless %} + {% apply spaceless %} {{ 'Export'|trans }} - {% endspaceless %} + {% endapply %}
{% endblock grid_exports %} {# --------------------------------------------------- grid_tweaks ------------------------------------------------- #} {% block grid_tweaks %}
- {% spaceless %} + {% apply spaceless %} {{ 'Tweaks'|trans }} - {% endspaceless %} + {% endapply %}
{% endblock grid_tweaks %} {# ------------------------------------------------ grid_column_actions_cell --------------------------------------------- #} @@ -234,7 +244,13 @@ {% set actions = column.getActionsToRender(row) %} {% endblock grid_column_actions_cell %} @@ -263,7 +279,10 @@ {% for key, index in values -%} {% set value = index %} {% set sourceValue = sourceValues[key] %} - {{ block('grid_column_cell') | raw }}{{ column.separator | raw }} + {{ block('grid_column_cell') | raw }} + {% if not loop.last %} + {{ column.separator | raw }} + {% endif %} {%- endfor %} {% endblock grid_column_array_cell %} {% block grid_column_type_array_cell %} @@ -280,24 +299,28 @@ {% block grid_column_type_join_cell %} {{ block('grid_column_join_cell') }} {% endblock grid_column_type_join_cell %} +{# ------------------------------------------------ grid_column_type_simple_array_columns --------------------------------------------- #} +{% block grid_column_type_simple_array_cell %} + {{ block('grid_column_array_cell') }} +{% endblock grid_column_type_simple_array_cell %} {# ------------------------------------------- grid_column_cell ---------------------------------------- #} {% block grid_column_cell %} -{%- spaceless %} +{% apply spaceless %} {% if column.filterable and column.searchOnClick %} {% set sourceValue = sourceValue is defined ? sourceValue : row.field(column.id) %} {{ value }} -{% elseif column.safe is sameas(false) %} +{% elseif column.safe is same as (false) %} {{ value|raw }} {% else %} {{ value|escape(column.safe)|raw }} {% endif %} -{% endspaceless -%} +{% endapply -%} {% endblock grid_column_cell %} {# -------------------------------------------- grid_column_operator --------------------------------------- #} {% block grid_column_operator %} {% if column.operatorsVisible %} - {% for operator in column.operators %} {% endfor %} @@ -311,14 +334,14 @@ {% set btweOperator = constant('APY\\DataGridBundle\\Grid\\Column\\Column::OPERATOR_BTWE') %} {% set isNullOperator = constant('APY\\DataGridBundle\\Grid\\Column\\Column::OPERATOR_ISNULL') %} {% set isNotNullOperator = constant('APY\\DataGridBundle\\Grid\\Column\\Column::OPERATOR_ISNOTNULL') %} -{% set op = column.data.operator is defined ? column.data.operator : column.defaultOperator %} -{% set from = column.data.from is defined ? column.data.from : null %} -{% set to = column.data.to is defined ? column.data.to : null %} +{% set op = column.data.operator ?? column.defaultOperator %} +{% set from = column.data.from ?? null %} +{% set to = column.data.to ?? null %} {{ grid_column_operator(column, grid, op, submitOnChange) }} - - + + {% endblock grid_column_filter_type_input %} @@ -328,9 +351,9 @@ {% set btweOperator = constant('APY\\DataGridBundle\\Grid\\Column\\Column::OPERATOR_BTWE') %} {% set isNullOperator = constant('APY\\DataGridBundle\\Grid\\Column\\Column::OPERATOR_ISNULL') %} {% set isNotNullOperator = constant('APY\\DataGridBundle\\Grid\\Column\\Column::OPERATOR_ISNOTNULL') %} -{% set op = column.data.operator is defined ? column.data.operator : column.defaultOperator %} -{% set from = column.data.from is defined ? column.data.from : null %} -{% set to = column.data.to is defined ? column.data.to : null %} +{% set op = column.data.operator ?? column.defaultOperator %} +{% set from = column.data.from ?? null %} +{% set to = column.data.to ?? null %} {% set multiple = column.selectMulti %} {% set expanded = column.selectExpanded %} @@ -339,23 +362,23 @@ {% if expanded %} {% for key, value in column.values %} - + {% endfor %} {% for key, value in column.values %} - + {% endfor %} {% if multiple %}{% endif %} {% else %} - + {% for key, value in column.values %} {% endfor %} - {% for key, value in column.values %} @@ -397,7 +420,14 @@ {% block grid_scripts_goto %} function {{ grid.hash }}_goto(url) { - window.location.href = url; + if (/MSIE (8\.\d+);/.test(navigator.userAgent)) { + var referLink = document.createElement('a'); + referLink.href = url; + document.body.appendChild(referLink); + referLink.click(); + } else { + window.location.href = url; + } return false; } diff --git a/Resources/views/blocks_js.jquery.html.twig b/Resources/views/blocks_js.jquery.html.twig index e7c57b22..7fe0c813 100644 --- a/Resources/views/blocks_js.jquery.html.twig +++ b/Resources/views/blocks_js.jquery.html.twig @@ -1,4 +1,4 @@ -{% extends 'APYDataGridBundle::blocks.html.twig' %} +{% extends '@APYDataGrid/blocks.html.twig' %} {% block grid_scripts_goto %} function {{ grid.hash }}_goto(url, data, type) @@ -31,10 +31,10 @@ function {{ grid.hash }}_submitForm(event, form) if ($(this).attr('multiple') == 'multiple') { for(var i= 0; i < value.length; i++) { - data += '&' + name + '=' + value[i]; + data += '&' + name + '=' + encodeURIComponent(value[i]); } } else { - data += '&' + name + '=' + value; + data += '&' + name + '=' + encodeURIComponent(value); } } else { data += '&' + name + '='; diff --git a/Tests/Action/MassActionTest.php b/Tests/Action/MassActionTest.php new file mode 100644 index 00000000..a91cb2b2 --- /dev/null +++ b/Tests/Action/MassActionTest.php @@ -0,0 +1,136 @@ + 'foo', 'bar' => 'bar']; + + private string $role = 'ROLE_FOO'; + + public function testMassActionConstruct() + { + $this->assertEquals($this->title, $this->massAction->getTitle()); + $this->assertEquals($this->callback, $this->massAction->getCallback()); + $this->assertEquals($this->confirm, $this->massAction->getConfirm()); + $this->assertEquals($this->parameters, $this->massAction->getParameters()); + $this->assertEquals($this->role, $this->massAction->getRole()); + } + + public function testSetTile() + { + $title = 'bar'; + $this->massAction->setTitle($title); + + $this->assertEquals($title, $this->massAction->getTitle()); + } + + public function testGetTitle() + { + $title = 'foobar'; + $this->massAction->setTitle($title); + + $this->assertEquals($title, $this->massAction->getTitle()); + } + + public function testSetCallback() + { + $callback = 'self::fooMassAction'; + $this->massAction->setCallback($callback); + + $this->assertEquals($callback, $this->massAction->getCallback()); + } + + public function testGetCallback() + { + $callback = 'self::barMassAction'; + $this->massAction->setCallback($callback); + + $this->assertEquals($callback, $this->massAction->getCallback()); + } + + public function testSetConfirm() + { + $confirm = false; + $this->massAction->setConfirm($confirm); + + $this->assertEquals($confirm, $this->massAction->getConfirm()); + } + + public function testGetConfirm() + { + $confirm = false; + $this->massAction->setConfirm($confirm); + + $this->assertFalse($this->massAction->getConfirm()); + } + + public function testDefaultConfirmMessage() + { + $this->assertIsString($this->massAction->getConfirmMessage()); + } + + public function testSetConfirmMessage() + { + $message = 'A foo test message'; + $this->massAction->setConfirmMessage($message); + + $this->assertEquals($message, $this->massAction->getConfirmMessage()); + } + + public function testGetConfirmMessage() + { + $message = 'A bar test message'; + $this->massAction->setConfirmMessage($message); + + $this->assertEquals($message, $this->massAction->getConfirmMessage()); + } + + public function testSetParameters() + { + $params = [1 => 1, 2 => 2]; + $this->massAction->setParameters($params); + + $this->assertEquals($params, $this->massAction->getParameters()); + } + + public function testGetParameters() + { + $params = [1, 2, 3]; + $this->massAction->setParameters($params); + + $this->assertEquals($params, $this->massAction->getParameters()); + } + + public function testSetRole() + { + $role = 'ROLE_ADMIN'; + $this->massAction->setRole($role); + + $this->assertEquals($role, $this->massAction->getRole()); + } + + public function testGetRole() + { + $role = 'ROLE_SUPER_ADMIN'; + $this->massAction->setRole($role); + + $this->assertEquals($role, $this->massAction->getRole()); + } + + public function setUp(): void + { + $this->massAction = new MassAction($this->title, $this->callback, $this->confirm, $this->parameters, $this->role); + } +} diff --git a/Tests/AddColumnTest.php b/Tests/AddColumnTest.php deleted file mode 100644 index 1910629e..00000000 --- a/Tests/AddColumnTest.php +++ /dev/null @@ -1,77 +0,0 @@ -col1 = $this->getMock('APY\DataGridBundle\Grid\Column\Column'); - $this->col2 = $this->getMock('APY\DataGridBundle\Grid\Column\Column'); - $this->col3 = $this->getMock('APY\DataGridBundle\Grid\Column\Column'); - $this->newCol = $this->getMock('APY\DataGridBundle\Grid\Column\Column'); - } - - public function testAddColumnPositiveOffset() - { - $columns = $this->getBaseColumns(); - $columns->addColumn($this->newCol, 1); - $this->assertAttributeEquals(array($this->newCol, $this->col1, $this->col2, $this->col3), 'columns', $columns); - - $columns = $this->getBaseColumns(); - $columns->addColumn($this->newCol, 2); - $this->assertAttributeEquals(array($this->col1, $this->newCol, $this->col2, $this->col3), 'columns', $columns); - - $columns = $this->getBaseColumns(); - $columns->addColumn($this->newCol, 3); - $this->assertAttributeEquals(array($this->col1, $this->col2, $this->newCol, $this->col3), 'columns', $columns); - - $columns = $this->getBaseColumns(); - $columns->addColumn($this->newCol, 4); - $this->assertAttributeEquals(array($this->col1, $this->col2, $this->col3, $this->newCol), 'columns', $columns); - - $columns = $this->getBaseColumns(); - $columns->addColumn($this->newCol, 5); - $this->assertAttributeEquals(array($this->col1, $this->col2, $this->col3, $this->newCol), 'columns', $columns); - } - - public function testAddColumnNullOffset() - { - $columns = $this->getBaseColumns(); - $columns->addColumn($this->newCol); - $this->assertAttributeEquals(array($this->col1, $this->col2, $this->col3, $this->newCol), 'columns', $columns); - } - - public function testAddColumnNegativeOffset() - { - $columns = $this->getBaseColumns(); - $columns->addColumn($this->newCol, -1); - $this->assertAttributeEquals(array($this->col1, $this->col2, $this->newCol, $this->col3), 'columns', $columns); - - $columns = $this->getBaseColumns(); - $columns->addColumn($this->newCol, -2); - $this->assertAttributeEquals(array($this->col1, $this->newCol, $this->col2, $this->col3), 'columns', $columns); - - $columns = $this->getBaseColumns(); - $columns->addColumn($this->newCol, -3); - $this->assertAttributeEquals(array($this->newCol, $this->col1, $this->col2, $this->col3), 'columns', $columns); - - $columns = $this->getBaseColumns(); - $columns->addColumn($this->newCol, -4); - $this->assertAttributeEquals(array($this->newCol, $this->col1, $this->col2, $this->col3), 'columns', $columns); - } - - protected function getBaseColumns() - { - $context = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface'); - $columns = new Columns($context); - $columns->addColumn($this->col1); - $columns->addColumn($this->col2); - $columns->addColumn($this->col3); - $this->assertAttributeEquals(array($this->col1, $this->col2, $this->col3), 'columns', $columns); - return $columns; - } -} - diff --git a/Tests/ByPassFinalHook.php b/Tests/ByPassFinalHook.php new file mode 100644 index 00000000..c7ceae03 --- /dev/null +++ b/Tests/ByPassFinalHook.php @@ -0,0 +1,10 @@ +assertEquals(true, $ma->getConfirm()); + } + + public function testConstructWithoutConfirmation() + { + $ma = new DeleteMassAction(); + $this->assertEquals(false, $ma->getConfirm()); + } +} diff --git a/Tests/Grid/Action/RowActionTest.php b/Tests/Grid/Action/RowActionTest.php new file mode 100644 index 00000000..0dc5bd75 --- /dev/null +++ b/Tests/Grid/Action/RowActionTest.php @@ -0,0 +1,328 @@ + 'foo', 'bar' => 'bar']; + + private string $role = 'ROLE_FOO'; + + private array $callbacks = []; + + private \APY\DataGridBundle\Grid\Action\RowAction $rowAction; + + /** @var \PHPUnit_Framework_MockObject_MockObject */ + private $row; + + public function testSetTitle() + { + $title = 'foo_title'; + $this->rowAction->setTitle($title); + + $this->assertEquals($title, $this->rowAction->getTitle()); + } + + public function testGetTitle() + { + $title = 'foo_title'; + $this->rowAction->setTitle($title); + + $this->assertEquals($title, $this->rowAction->getTitle()); + } + + public function testSetRoute() + { + $route = 'another_vendor.another_bundle.controller.route_name'; + $this->rowAction->setRoute($route); + + $this->assertEquals($route, $this->rowAction->getRoute()); + } + + public function testGetRoute() + { + $route = 'another_vendor.another_bundle.controller.route_name'; + $this->rowAction->setRoute($route); + + $this->assertEquals($route, $this->rowAction->getRoute()); + } + + public function testSetConfirm() + { + $confirm = true; + $this->rowAction->setConfirm($confirm); + + $this->assertEquals(true, $this->rowAction->getConfirm()); + } + + public function testGetConfirmation() + { + $confirm = true; + $this->rowAction->setConfirm($confirm); + + $this->assertTrue($this->rowAction->getConfirm()); + } + + public function testDefaultConfirmMessage() + { + $this->assertIsString($this->rowAction->getConfirmMessage()); + } + + public function testSetConfirmMessage() + { + $message = 'A foo test message'; + $this->rowAction->setConfirmMessage($message); + + $this->assertEquals($message, $this->rowAction->getConfirmMessage()); + } + + public function testGetConfirmMessage() + { + $message = 'A bar test message'; + $this->rowAction->setConfirmMessage($message); + + $this->assertEquals($message, $this->rowAction->getConfirmMessage()); + } + + public function testSetTarget() + { + $target = '_self'; + $this->rowAction->setTarget($target); + + $this->assertEquals($target, $this->rowAction->getTarget()); + } + + public function testGetTarget() + { + $target = '_blank'; + $this->rowAction->setTarget($target); + + $this->assertEquals($target, $this->rowAction->getTarget()); + } + + public function testSetColumn() + { + $col = 'foo'; + $this->rowAction->setColumn($col); + + $this->assertEquals($col, $this->rowAction->getColumn()); + } + + public function testGetColumn() + { + $col = 'bar'; + $this->rowAction->setColumn($col); + + $this->assertEquals($col, $this->rowAction->getColumn()); + } + + public function testAddRouteParameters() + { + $stringParam = 'aParam'; + $this->rowAction->addRouteParameters($stringParam); + + $string2Param = 'secondStringParam'; + $this->rowAction->addRouteParameters($string2Param); + + $intKeyParam = [1 => 'paramOne', 2 => 'paramTwo']; + $this->rowAction->addRouteParameters($intKeyParam); + + $associativeParam = ['foo' => 'fooParam', 'bar' => 'barParam']; + $this->rowAction->addRouteParameters($associativeParam); + + $this->assertEquals( + array_merge([0 => $stringParam, 1 => $string2Param, 2 => $intKeyParam[1], 3 => $intKeyParam[2]], $associativeParam), + $this->rowAction->getRouteParameters() + ); + } + + public function testSetStringRouteParameters() + { + $param = 'param'; + $this->rowAction->setRouteParameters($param); + + $this->assertEquals([0 => $param], $this->rowAction->getRouteParameters()); + } + + public function testSetArrayRouteParameters() + { + $params = ['foo' => 'foo_param', 'bar' => 'bar_param']; + $this->rowAction->setRouteParameters($params); + + $this->assertEquals($params, $this->rowAction->getRouteParameters()); + } + + public function testGetRouteParameters() + { + $params = ['foo' => 'foo_param', 'bar' => 'bar_param']; + $this->rowAction->setRouteParameters($params); + + $this->assertEquals($params, $this->rowAction->getRouteParameters()); + } + + public function testSetRouteParametersMapping() + { + $routeParamsMapping = ['foo.bar.city' => 'cityId', 'foo.bar.country' => 'countryId']; + $this->rowAction->setRouteParametersMapping($routeParamsMapping); + + $this->assertEquals($routeParamsMapping['foo.bar.city'], $this->rowAction->getRouteParametersMapping('foo.bar.city')); + $this->assertEquals($routeParamsMapping['foo.bar.country'], $this->rowAction->getRouteParametersMapping('foo.bar.country')); + } + + public function testGetRouteParametersMapping() + { + $routeParamKey = 'foo.bar.city'; + $routeParamValue = 'cityId'; + $routeParamsMapping = [$routeParamKey => $routeParamValue]; + $this->rowAction->setRouteParametersMapping($routeParamsMapping); + + $this->assertEquals('cityId', $this->rowAction->getRouteParametersMapping('foo.bar.city')); + $this->assertNull($this->rowAction->getRouteParametersMapping('foo.bar.country')); + } + + public function testSetAttributes() + { + $attr = ['foo' => 'foo_val', 'bar' => 'bar_val']; + $this->rowAction->setAttributes($attr); + + $this->assertEquals($attr, $this->rowAction->getAttributes()); + } + + public function testAddAttribute() + { + $attrName = 'foo1'; + $attrVal = 'foo_val1'; + $this->rowAction->addAttribute($attrName, $attrVal); + + $this->assertEquals( + array_merge($this->attributes, [$attrName => $attrVal]), + $this->rowAction->getAttributes() + ); + } + + public function testGetAttributes() + { + $this->assertEquals($this->attributes, $this->rowAction->getAttributes()); + } + + public function testSetRole() + { + $role = 'ROLE_ADMIN'; + $this->rowAction->setRole($role); + + $this->assertEquals($role, $this->rowAction->getRole()); + } + + public function testGetRole() + { + $role = 'ROLE_SUPER_ADMIN'; + $this->rowAction->setRole($role); + + $this->assertEquals($role, $this->rowAction->getRole()); + } + + public function testManipulateRender() + { + $callback1 = fn() => 1; + $callback2 = fn() => 2; + + $this->rowAction->manipulateRender($callback1); + $this->rowAction->manipulateRender($callback2); + + $this->assertEquals([$callback1, $callback2], $this->rowAction->getCallbacks()); + } + + public function testAddManipulateRender() + { + $this->addCalbacks(); + $this->assertEquals($this->callbacks, $this->rowAction->getCallbacks()); + } + + private function addCalbacks() + { + $callback1 = function ($action, $row) { + /** @var $row Row */ + if ($row->getField('foo') == 0) { + return; + } + + return $action; + }; + + $this->rowAction->addManipulateRender($callback1); + + $callback2 = function ($action, $row) { + /** @var $row Row */ + if ($row->getField('bar') == 0) { + return; + } + + return $action; + }; + + $this->rowAction->addManipulateRender($callback2); + + $this->callbacks = [$callback1, $callback2]; + } + + public function testExecuteAllCallbacks() + { + $this->addCalbacks(); + + $this->row + ->expects($this->exactly(2)) + ->method('getField') + ->with($this->logicalOr('foo', 'bar')) + ->willReturn(1); + + $this->assertEquals($this->rowAction, $this->rowAction->render($this->row)); + } + + public function testStopOnFirstCallbackFailed() + { + $this->addCalbacks(); + + $this->row + ->expects($this->exactly(1)) + ->method('getField') + ->with('foo') + ->willReturn(0); + + $this->assertEquals(null, $this->rowAction->render($this->row)); + } + + public function testSetEnabled() + { + $enabled = true; + $this->rowAction->setEnabled($enabled); + + $this->assertEquals($enabled, $this->rowAction->getEnabled()); + } + + public function testGetEnabled() + { + $enabled = true; + $this->rowAction->setEnabled($enabled); + + $this->assertTrue($this->rowAction->getEnabled()); + } + + protected function setUp(): void + { + $this->rowAction = new RowAction( + $this->title, $this->route, $this->confirm, $this->target, $this->attributes, $this->role + ); + $this->row = $this->createMock(Row::class); + } +} diff --git a/Tests/Grid/Column/ActionsColumnTest.php b/Tests/Grid/Column/ActionsColumnTest.php new file mode 100644 index 00000000..ac458792 --- /dev/null +++ b/Tests/Grid/Column/ActionsColumnTest.php @@ -0,0 +1,173 @@ +createMock(RowAction::class); + $rowAction2 = $this->createMock(RowAction::class); + $column = new ActionsColumn($columnId, $columnTitle, [$rowAction1, $rowAction2]); + + $this->assertEquals([$rowAction1, $rowAction2], $column->getRowActions()); + $this->assertEquals($columnId, $column->getId()); + $this->assertEquals($columnTitle, $column->getTitle()); + $this->assertEquals(false, $column->isSortable()); + $this->assertEquals(false, $column->isVisibleForSource()); + $this->assertEquals(true, $column->isFilterable()); + } + + public function testGetType() + { + $this->assertEquals('actions', $this->column->getType()); + } + + public function testGetFilterType() + { + $this->assertEquals('actions', $this->column->getFilterType()); + } + + public function testGetActionsToRender() + { + $row = $this->createMock(Row::class); + + $rowAction1 = $this->createMock(RowAction::class); + $rowAction1->method('render')->with($row)->willReturn(null); + $rowAction2 = $this->createMock(RowAction::class); + $rowAction2->method('render')->with($row)->willReturn($rowAction2); + + $column = new ActionsColumn('columnId', 'columnTitle', [ + $rowAction1, + $rowAction2, + ]); + + $this->assertEquals([1 => $rowAction2], $column->getActionsToRender($row)); + } + + public function testGetRowActions() + { + $rowAction1 = $this->createMock(RowAction::class); + $rowAction2 = $this->createMock(RowAction::class); + $column = new ActionsColumn('columnId', 'columnTitle', [ + $rowAction1, + $rowAction2, + ]); + + $this->assertEquals([$rowAction1, $rowAction2], $column->getRowActions()); + } + + public function testSetRowActions() + { + $rowAction1 = $this->createMock(RowAction::class); + $rowAction2 = $this->createMock(RowAction::class); + $column = new ActionsColumn('columnId', 'columnTitle', []); + $column->setRowActions([$rowAction1, $rowAction2]); + + $this->assertEquals([$rowAction1, $rowAction2], $column->getRowActions()); + } + + public function testIsNotVisibleIfExported() + { + $isExported = true; + $this->assertFalse($this->column->isVisible($isExported)); + } + + public function testIsVisibleIfNotExportedAndNoAuthChecker() + { + $this->assertTrue($this->column->isVisible()); + } + + public function testIsVisibleIfNotExportedNoAuthCheckerAndNotRole() + { + $this->column->setAuthorizationChecker($this->createMock(AuthorizationCheckerInterface::class)); + $this->assertTrue($this->column->isVisible()); + } + + public function testIsVisibleIfAuthCheckerIsGranted() + { + $role = 'ROLE_USER'; + $this->column->setRole($role); + + $authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $authChecker->method('isGranted')->with($role)->willReturn(true); + $this->column->setAuthorizationChecker($authChecker); + + $this->assertTrue($this->column->isVisible()); + } + + public function testIsNotVisibleIfAuthCheckerIsNotGranted() + { + $role = 'ROLE_USER'; + $this->column->setRole($role); + + $authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $authChecker->method('isGranted')->with($role)->willReturn(false); + $this->column->setAuthorizationChecker($authChecker); + + $this->assertFalse($this->column->isVisible()); + } + + public function testGetPrimaryFieldAsRouteParametersIfRouteParametersNotSetted() + { + $row = $this->createMock(Row::class); + $row->method('getPrimaryField')->willReturn('id'); + $row->method('getPrimaryFieldValue')->willReturn(1); + + $rowAction = $this->createMock(RowAction::class); + $rowAction->method('getRouteParameters')->willReturn([]); + + $this->assertEquals(['id' => 1], $this->column->getRouteParameters($row, $rowAction)); + } + + public function testGetRouteParameters() + { + $row = $this->createMock(Row::class); + $row + ->method('getField') + ->withConsecutive(['foo.bar'], ['barFoo']) + ->willReturnOnConsecutiveCalls('testValue', 'aValue'); + + $rowAction = $this->createMock(RowAction::class); + $rowAction + ->method('getRouteParametersMapping') + ->withConsecutive(['foo.bar'], ['barFoo']) + ->willReturnOnConsecutiveCalls(null, 'aName'); + + $rowAction->method('getRouteParameters')->willReturn([ + 'foo' => 1, + 'foo.bar.foobar' => 2, + 1 => 'foo.bar', + '2' => 'barFoo', + ]); + + $this->assertEquals([ + 'foo' => 1, + 'fooBarFoobar' => 2, + 'fooBar' => 'testValue', + 'aName' => 'aValue', + ], $this->column->getRouteParameters($row, $rowAction)); + } + + public function setUp(): void + { + $rowAction1 = $this->createMock(RowAction::class); + $rowAction2 = $this->createMock(RowAction::class); + $this->column = new ActionsColumn('columnId', 'columnTitle', [ + $rowAction1, + $rowAction2, + ]); + } +} diff --git a/Tests/Grid/Column/ArrayColumnTest.php b/Tests/Grid/Column/ArrayColumnTest.php new file mode 100644 index 00000000..217222fb --- /dev/null +++ b/Tests/Grid/Column/ArrayColumnTest.php @@ -0,0 +1,146 @@ +assertEquals('array', $this->column->getType()); + } + + public function testInitializeDefaultParams() + { + $this->assertEquals([ + Column::OPERATOR_LIKE, + Column::OPERATOR_NLIKE, + Column::OPERATOR_EQ, + Column::OPERATOR_NEQ, + Column::OPERATOR_ISNULL, + Column::OPERATOR_ISNOTNULL, + ], $this->column->getOperators()); + } + + public function testDocumentFilters() + { + $value = ['foo', 'bar']; + + $this->column->setData(['operator' => Column::OPERATOR_EQ, 'from' => $value]); + + $this->assertEquals( + [new Filter(Column::OPERATOR_EQ, $value, null)], + $this->column->getFilters('document') + ); + } + + public function testEqualFilter() + { + $value = ['foo', 'foobar']; + + $this->column->setData(['operator' => Column::OPERATOR_EQ, 'from' => $value]); + + $this->assertEquals( + [new Filter(Column::OPERATOR_EQ, 'a:2:{i:1;s:3:"foo";i:2;s:6:"foobar";}')], + $this->column->getFilters('asource') + ); + } + + public function testNotEqualFilter() + { + $value = ['foo', 'foobar']; + + $this->column->setData(['operator' => Column::OPERATOR_NEQ, 'from' => $value]); + + $this->assertEquals( + [new Filter(Column::OPERATOR_NEQ, 'a:2:{i:1;s:3:"foo";i:2;s:6:"foobar";}')], + $this->column->getFilters('asource') + ); + } + + public function testLikeFilter() + { + $value = ['foo']; + + $this->column->setData(['operator' => Column::OPERATOR_LIKE, 'from' => $value]); + + $this->assertEquals( + [new Filter(Column::OPERATOR_LIKE, 's:3:"foo";')], + $this->column->getFilters('asource') + ); + } + + public function testNotLikeFilter() + { + $value = ['foo']; + + $this->column->setData(['operator' => Column::OPERATOR_NLIKE, 'from' => $value]); + + $this->assertEquals( + [new Filter(Column::OPERATOR_NLIKE, 's:3:"foo";')], + $this->column->getFilters('asource') + ); + } + + public function testIsNullFilter() + { + $this->column->setData(['operator' => Column::OPERATOR_ISNULL]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_ISNULL), + new Filter(Column::OPERATOR_EQ, 'a:0:{}'), + ], $this->column->getFilters('asource')); + $this->assertEquals(Column::DATA_DISJUNCTION, $this->column->getDataJunction()); + } + + public function testIsNotNullFilter() + { + $this->column->setData(['operator' => Column::OPERATOR_ISNOTNULL]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_ISNOTNULL), + new Filter(Column::OPERATOR_NEQ, 'a:0:{}'), + ], $this->column->getFilters('asource')); + } + + public function testRenderCellWithoutCallback() + { + $values = ['foo' => 'a', 'bar' => 'b', 'foobar' => ['c', 'd']]; + + $result = $this->column->renderCell( + $values, + $this->createMock(Row::class), + $this->createMock(Router::class) + ); + + // @todo: is this the expected result? + $this->assertEquals($result, $values); + } + + public function testRenderCellWithCallback() + { + $values = ['foo' => 'a', 'bar' => 'b', 'foobar' => ['c', 'd']]; + $this->column->manipulateRenderCell(fn($value, $row, $router) => ['bar' => 'a', 'foo' => 'b']); + + $result = $this->column->renderCell( + $values, + $this->createMock(Row::class), + $this->createMock(Router::class) + ); + + $this->assertEquals($result, ['bar' => 'a', 'foo' => 'b']); + } + + public function setUp(): void + { + $this->column = new ArrayColumn(); + } +} diff --git a/Tests/Grid/Column/BlankColumnTest.php b/Tests/Grid/Column/BlankColumnTest.php new file mode 100644 index 00000000..3ed3a056 --- /dev/null +++ b/Tests/Grid/Column/BlankColumnTest.php @@ -0,0 +1,37 @@ +assertEquals('blank', $column->getType()); + } + + public function testInitialize() + { + $params = [ + 'filterable' => true, + 'sortable' => true, + 'foo' => false, + 'bar' => true, + ]; + + $column = new BlankColumn($params); + + $this->assertEquals([ + 'filterable' => false, + 'sortable' => false, + 'source' => false, + 'foo' => false, + 'bar' => true, + ], + $column->getparams()); + } +} diff --git a/Tests/Grid/Column/BooleanColumnTest.php b/Tests/Grid/Column/BooleanColumnTest.php new file mode 100644 index 00000000..4719f5bb --- /dev/null +++ b/Tests/Grid/Column/BooleanColumnTest.php @@ -0,0 +1,132 @@ +assertEquals('boolean', $this->column->getType()); + } + + public function testGetDisplayedValue() + { + $this->assertEquals(1, $this->column->getDisplayedValue(true)); + $this->assertEquals(0, $this->column->getDisplayedValue(false)); + $this->assertEquals('foo', $this->column->getDisplayedValue('foo')); + } + + public function testInitialize() + { + $params = [ + 'filter' => 'foo', + 'bar' => 'bar', + 'size' => 52, + ]; + + $column = new BooleanColumn($params); + + $this->assertEquals([ + 'filter' => 'select', + 'selectFrom' => 'values', + 'operators' => [Column::OPERATOR_EQ], + 'defaultOperator' => Column::OPERATOR_EQ, + 'operatorsVisible' => false, + 'selectMulti' => false, + 'bar' => 'bar', + 'size' => 52, + ], $column->getParams()); + } + + public function testInitializeAlignment() + { + $this->assertEquals(Column::ALIGN_CENTER, $this->column->getAlign()); + + $column = new BooleanColumn(['align' => Column::ALIGN_LEFT]); + $this->assertEquals(Column::ALIGN_LEFT, $column->getAlign()); + } + + public function testInitializeSize() + { + $this->assertEquals(30, $this->column->getSize()); + + $column = new BooleanColumn(['size' => 40]); + $this->assertEquals(40, $column->getSize()); + } + + public function testInitializeValues() + { + $this->assertEquals([1 => 'true', 0 => 'false'], $this->column->getValues()); + + $values = [1 => 'foo', 0 => 'bar']; + $params = ['values' => $values]; + $column = new BooleanColumn($params); + $this->assertEquals($values, $column->getValues()); + } + + public function testIsQueryValid() + { + // It seems that's no way for this to return false + + $this->assertTrue($this->column->isQueryValid(true)); + $this->assertTrue($this->column->isQueryValid(false)); + $this->assertTrue($this->column->isQueryValid(1)); + $this->assertTrue($this->column->isQueryValid(0)); + // Doesn't work with PHP 7 + // $this->assertFalse($this->column->isQueryValid('foo')); + } + + public function testRenderCell() + { + $this->assertEquals('true', $this->column->renderCell( + true, $this->createMock(Row::class), $this->createMock(Router::class) + )); + + $this->assertEquals('true', $this->column->renderCell( + 1, $this->createMock(Row::class), $this->createMock(Router::class) + )); + + $this->assertEquals('false', $this->column->renderCell( + 0, $this->createMock(Row::class), $this->createMock(Router::class) + )); + } + + public function testRenderCellWithCallback() + { + $this->column->manipulateRenderCell( + fn($value, $row, $router) => 'true' + ); + $this->assertEquals('true', $this->column->renderCell( + 0, $this->createMock(Row::class), $this->createMock(Router::class) + )); + + $this->column->manipulateRenderCell( + fn($value, $row, $router) => 'false' + ); + $this->assertEquals('false', $this->column->renderCell( + 1, $this->createMock(Row::class), $this->createMock(Router::class) + )); + + $this->column->manipulateRenderCell( + function ($value, $row, $router) { + return; + } + ); + $this->assertEquals('false', $this->column->renderCell( + 1, $this->createMock(Row::class), $this->createMock(Router::class) + )); + } + + public function setUp(): void + { + $this->column = new BooleanColumn(); + } +} diff --git a/Tests/Grid/Column/ColumnTest.php b/Tests/Grid/Column/ColumnTest.php new file mode 100644 index 00000000..159b7a8e --- /dev/null +++ b/Tests/Grid/Column/ColumnTest.php @@ -0,0 +1,1401 @@ +getMockForAbstractClass(Column::class); + + $field = 'field'; + + $mock->__initialize(['field' => $field]); + + $this->assertEquals($field, $mock->getTitle()); + $this->assertEquals(true, $mock->isSortable()); + $this->assertEquals(true, $mock->isVisible()); + $this->assertEquals(-1, $mock->getSize()); + $this->assertEquals(true, $mock->isFilterable()); + $this->assertEquals(false, $mock->isVisibleForSource()); + $this->assertEquals(false, $mock->isPrimary()); + $this->assertEquals(Column::ALIGN_LEFT, $mock->getAlign()); + $this->assertEquals('text', $mock->getInputType()); + $this->assertEquals('input', $mock->getFilterType()); + $this->assertEquals('query', $mock->getSelectFrom()); + $this->assertEquals([], $mock->getValues()); + $this->assertEquals(true, $mock->getOperatorsVisible()); + $this->assertEquals(false, $mock->getIsManualField()); + $this->assertEquals(false, $mock->getIsAggregate()); + $this->assertEquals(true, $mock->getUsePrefixTitle()); + $this->assertEquals(Column::getAvailableOperators(), $mock->getOperators()); + $this->assertEquals(Column::OPERATOR_LIKE, $mock->getDefaultOperator()); + $this->assertEquals(false, $mock->getSelectMulti()); + $this->assertEquals(false, $mock->getSelectExpanded()); + $this->assertEquals(false, $mock->getSearchOnClick()); + $this->assertEquals('html', $mock->getSafe()); + $this->assertEquals('
', $mock->getSeparator()); + } + + public function testInitialize() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $id = 'id'; + $title = 'title'; + $sortable = false; + $visible = false; + $size = 2; + $filterable = false; + $source = true; + $primary = true; + $align = Column::ALIGN_RIGHT; + $inputType = 'number'; + $field = 'field'; + $role = 'role'; + $order = 1; + $joinType = 'left'; + $filter = 'filter'; + $selectFrom = 'source'; + $values = [1, 2, 3]; + $operatorsVisible = false; + $isManualField = true; + $isAggregate = true; + $usePrefixText = false; + $operators = [Column::OPERATOR_ISNULL, Column::OPERATOR_ISNOTNULL]; + $defaultOperator = Column::OPERATOR_ISNOTNULL; + $selectMulti = true; + $selectExpanded = true; + $searchOnClick = true; + $safe = 'safe'; + $separator = '
'; + $export = true; + $class = 'class'; + $translationDomain = 'en_EN'; + + $params = [ + 'id' => $id, + 'title' => $title, + 'sortable' => $sortable, + 'visible' => $visible, + 'size' => $size, + 'filterable' => $filterable, + 'source' => $source, + 'primary' => $primary, + 'align' => $align, + 'inputType' => $inputType, + 'field' => $field, + 'role' => $role, + 'order' => $order, + 'joinType' => $joinType, + 'filter' => $filter, + 'selectFrom' => $selectFrom, + 'values' => $values, + 'operatorsVisible' => $operatorsVisible, + 'isManualField' => $isManualField, + 'isAggregate' => $isAggregate, + 'usePrefixTitle' => $usePrefixText, + 'operators' => $operators, + 'defaultOperator' => $defaultOperator, + 'selectMulti' => $selectMulti, + 'selectExpanded' => $selectExpanded, + 'searchOnClick' => $searchOnClick, + 'safe' => $safe, + 'separator' => $separator, + 'export' => $export, + 'class' => $class, + 'translation_domain' => $translationDomain, + ]; + + $mock->__initialize($params); + + $this->assertEquals($params, $mock->getParams()); + $this->assertEquals($id, $mock->getId()); + $this->assertEquals($title, $mock->getTitle()); + $this->assertEquals($sortable, $mock->isSortable()); + $this->assertEquals($visible, $mock->isVisible()); + $this->assertEquals($size, $mock->getSize()); + $this->assertEquals($filterable, $mock->isFilterable()); + $this->assertEquals($source, $mock->isVisibleForSource()); + $this->assertEquals($primary, $mock->isPrimary()); + $this->assertEquals($align, $mock->getAlign()); + $this->assertEquals($inputType, $mock->getInputType()); + $this->assertEquals($field, $mock->getField()); + $this->assertEquals($role, $mock->getRole()); + $this->assertEquals($order, $mock->getOrder()); + $this->assertEquals($joinType, $mock->getJoinType()); + $this->assertEquals($filter, $mock->getFilterType()); + $this->assertEquals($selectFrom, $mock->getSelectFrom()); + $this->assertEquals($values, $mock->getValues()); + $this->assertEquals($operatorsVisible, $mock->getOperatorsVisible()); + $this->assertEquals($isManualField, $mock->getIsManualField()); + $this->assertEquals($isAggregate, $mock->getIsAggregate()); + $this->assertEquals($usePrefixText, $mock->getUsePrefixTitle()); + $this->assertEquals($operators, $mock->getOperators()); + $this->assertEquals($defaultOperator, $mock->getDefaultOperator()); + $this->assertEquals($selectMulti, $mock->getSelectMulti()); + $this->assertEquals($selectExpanded, $mock->getSelectExpanded()); + $this->assertEquals($searchOnClick, $mock->getSearchOnClick()); + $this->assertEquals($safe, $mock->getSafe()); + $this->assertEquals($separator, $mock->getSeparator()); + $this->assertEquals($export, $mock->getExport()); + $this->assertEquals($class, $mock->getClass()); + $this->assertEquals($translationDomain, $mock->getTranslationDomain()); + } + + public function testRenderCellWithCallback() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $value = 0; + $row = $this->createMock(Row::class); + $router = $this->createMock(Router::class); + + $mock->manipulateRenderCell(fn($value, $row, $router) => 1); + + $this->assertEquals(1, $mock->renderCell($value, $row, $router)); + } + + public function testRenderCellWithBoolValue() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $row = $this->createMock(Row::class); + $router = $this->createMock(Router::class); + + $mock->setValues([1 => 'foo']); + $this->assertEquals('foo', $mock->renderCell(1, $row, $router)); + + $mock->setValues(['1' => 'bar']); + $this->assertEquals('bar', $mock->renderCell('1', $row, $router)); + } + + public function testRenderCell() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $row = $this->createMock(Row::class); + $router = $this->createMock(Router::class); + + $mock->setValues(['foo' => 'bar']); + $this->assertEquals('bar', $mock->renderCell('foo', $row, $router)); + } + + public function testManipulateRenderCell() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $value = 0; + $row = $this->createMock(Row::class); + $router = $this->createMock(Router::class); + + $callback = fn($value, $row, $router) => 1; + $mock->manipulateRenderCell($callback); + + $this->assertEquals($callback, $mock->getCallback()); + } + + public function testSetId() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setId(1); + + $this->assertEquals(1, $mock->getId()); + } + + public function testGetId() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setId(1); + + $this->assertEquals(1, $mock->getId()); + } + + public function testGetRenderBlockId() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setId('foo.bar:foobar'); + + $this->assertEquals('foo_bar_foobar', $mock->getRenderBlockId()); + } + + public function testSetTitle() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $title = 'title'; + $mock->setTitle($title); + + $this->assertEquals($title, $mock->getTitle()); + } + + public function testGetTitle() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $title = 'title'; + $mock->setTitle($title); + + $this->assertEquals($title, $mock->getTitle()); + } + + public function testSetVisible() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $isVisible = true; + $mock->setVisible($isVisible); + + $this->assertEquals($isVisible, $mock->isVisible()); + } + + public function testItIsNotVisibleWhenNotExported() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $isVisible = false; + $mock->setVisible($isVisible); + + $exported = false; + $this->assertFalse($mock->isVisible($exported)); + } + + public function testItIsVisibleIfNotExported() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $isVisible = true; + $mock->setVisible($isVisible); + + $exported = false; + $this->assertTrue($mock->isVisible($exported)); + } + + public function testItIsVisibleIfNotExportedAndRoleNotSetted() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $isVisible = true; + $mock->setVisible($isVisible); + $mock->setAuthorizationChecker($this->createMock(AuthorizationCheckerInterface::class)); + + $exported = false; + $this->assertTrue($mock->isVisible($exported)); + } + + public function testItIsVisibleIfNotExportedAndGranted() + { + $mock = $this->getMockForAbstractClass(Column::class); + $role = 'ROLE_USER'; + $authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $authChecker->method('isGranted')->with($role)->willReturn(true); + + $isVisible = true; + $mock->setVisible($isVisible); + $mock->setAuthorizationChecker($authChecker); + $mock->setRole($role); + + $exported = false; + $this->assertTrue($mock->isVisible($exported)); + } + + public function testItIsNotVisibleIfNotExportedButNotGranted() + { + $mock = $this->getMockForAbstractClass(Column::class); + $role = 'ROLE_USER'; + $authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $authChecker->method('isGranted')->with($role)->willReturn(false); + + $isVisible = true; + $mock->setVisible($isVisible); + $mock->setAuthorizationChecker($authChecker); + $mock->setRole($role); + + $exported = false; + $this->assertFalse($mock->isVisible($exported)); + } + + public function testItIsNotVisibleWhenExported() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $export = false; + $mock->setExport($export); + + $exported = true; + $this->assertFalse($mock->isVisible($exported)); + } + + public function testItIsVisibleIfExported() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $export = true; + $mock->setExport($export); + + $exported = true; + $this->assertTrue($mock->isVisible($exported)); + } + + public function testItIsVisibleIfExportedAndRoleNotSetted() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $export = true; + $mock->setExport($export); + $mock->setAuthorizationChecker($this->createMock(AuthorizationCheckerInterface::class)); + + $exported = true; + $this->assertTrue($mock->isVisible($exported)); + } + + public function testItIsVisibleIfExportedAndGranted() + { + $mock = $this->getMockForAbstractClass(Column::class); + $role = 'ROLE_USER'; + $authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $authChecker->method('isGranted')->with($role)->willReturn(true); + + $export = true; + $mock->setExport($export); + $mock->setAuthorizationChecker($authChecker); + $mock->setRole($role); + + $exported = true; + $this->assertTrue($mock->isVisible($exported)); + } + + public function testItIsNotVisibleIfExportedButNotGranted() + { + $mock = $this->getMockForAbstractClass(Column::class); + $role = 'ROLE_USER'; + $authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $authChecker->method('isGranted')->with($role)->willReturn(false); + + $export = true; + $mock->setExport($export); + $mock->setAuthorizationChecker($authChecker); + $mock->setRole($role); + + $exported = true; + $this->assertFalse($mock->isVisible($exported)); + } + + public function testIsNotSortedWhenNotOrdered() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $this->assertEquals(false, $mock->isSorted()); + } + + public function testIsSortedWhenOrdered() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setOrder(1); + + $this->assertEquals(true, $mock->isSorted()); + } + + public function testSetSortable() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSortable(true); + + $this->assertEquals(true, $mock->isSortable()); + } + + public function testIsSortable() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSortable(true); + + $this->assertTrue(true, $mock->isSortable()); + } + + public function testIsNotFilteredIfNeitherOperatorNorOperandsSetted() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $this->assertFalse($mock->isFiltered()); + } + + public function testIsNotFilteredIfFromOperandHasDefaultValue() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['from' => Column::DEFAULT_VALUE]); + + $this->assertFalse($mock->isFiltered()); + } + + public function testIsNotFilteredIfToOperandHasDefaultValue() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['to' => Column::DEFAULT_VALUE]); + + $this->assertFalse($mock->isFiltered()); + } + + public function testIsNotFilteredIfOperatorNeitherIsIsNullNorIsNotNull() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_LIKE]); + + $this->assertFalse($mock->isFiltered()); + } + + public function testIsFilteredIfFromOperandHasValueDifferentThanDefault() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['from' => 1]); + + $this->assertTrue($mock->isFiltered()); + } + + public function testIsFilteredIfToOperandHasValueDifferentThanDefault() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['to' => 1]); + + $this->assertTrue($mock->isFiltered()); + } + + public function testIsFilteredIfOperatorIsNull() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_ISNULL]); + + $this->assertTrue($mock->isFiltered()); + } + + public function testIsFilteredIfOperatorIsNotNull() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_ISNOTNULL]); + + $this->assertTrue($mock->isFiltered()); + } + + public function testSetFilterable() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setFilterable(true); + + $this->assertEquals(true, $mock->isFilterable()); + } + + public function testIsFilterable() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setFilterable(true); + + $this->assertTrue($mock->isFilterable()); + } + + public function testItDoesNotSetOrderIfOrderIsNull() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setOrder(null); + + $this->assertEquals(null, $mock->getOrder()); + $this->assertEquals(false, $mock->isSorted()); + } + + public function testItDoesSetOrderIfZero() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setOrder(0); + + $this->assertEquals(0, $mock->getOrder()); + $this->assertEquals(true, $mock->isSorted()); + } + + public function testItDoesSetOrder() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setOrder(1); + + $this->assertEquals(1, $mock->getOrder()); + $this->assertEquals(true, $mock->isSorted()); + } + + public function testGetOrder() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setOrder(1); + + $this->assertEquals(1, $mock->getOrder()); + } + + public function testRaiseExceptionIfSizeNotAllowed() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $this->expectException(\InvalidArgumentException::class); + + $mock->setSize(-2); + } + + public function testAutoResize() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSize(-1); + + $this->assertEquals(-1, $mock->getSize()); + } + + public function testSetSize() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSize(2); + + $this->assertEquals(2, $mock->getSize()); + } + + public function testGetSize() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSize(3); + + $this->assertEquals(3, $mock->getSize()); + } + + public function testDataDefaultIfNoDataSetted() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData([]); + + $this->assertEquals([ + 'operator' => Column::OPERATOR_LIKE, + 'from' => Column::DEFAULT_VALUE, + 'to' => Column::DEFAULT_VALUE, + ], $mock->getRawData()); + } + + public function testSetNullOperatorWithoutFromToValues() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_ISNULL]); + + $this->assertEquals([ + 'operator' => Column::OPERATOR_ISNULL, + 'from' => Column::DEFAULT_VALUE, + 'to' => Column::DEFAULT_VALUE, + ], $mock->getRawData()); + } + + public function testSetNotNullOperatorWithoutFromToValues() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_ISNOTNULL]); + + $this->assertEquals([ + 'operator' => Column::OPERATOR_ISNOTNULL, + 'from' => Column::DEFAULT_VALUE, + 'to' => Column::DEFAULT_VALUE, + ], $mock->getRawData()); + } + + public function testDoesNotSetDataIfOperatorNotNotNullOrNullNoFromToValues() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $operators = array_flip(Column::getAvailableOperators()); + unset($operators[Column::OPERATOR_ISNOTNULL]); + unset($operators[Column::OPERATOR_ISNULL]); + + foreach (array_keys($operators) as $operator) { + $mock->setData(['operator' => $operator]); + + $this->assertEquals([ + 'operator' => Column::OPERATOR_LIKE, + 'from' => Column::DEFAULT_VALUE, + 'to' => Column::DEFAULT_VALUE, + ], $mock->getRawData()); + } + } + + public function testItSetsData() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $operators = array_flip(Column::getAvailableOperators()); + unset($operators[Column::OPERATOR_ISNOTNULL]); + unset($operators[Column::OPERATOR_ISNULL]); + + foreach (array_keys($operators) as $operator) { + $mock->setData(['operator' => $operator, 'from' => 'from', 'to' => 'to']); + + $this->assertEquals([ + 'operator' => $operator, + 'from' => 'from', + 'to' => 'to', + ], $mock->getData()); + } + } + + public function testGetDataNullOpearatorWithoutValues() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_ISNULL]); + + $this->assertEquals([ + 'operator' => Column::OPERATOR_ISNULL, + ], $mock->getData()); + } + + public function testGetDataNotNullOpearatorWithoutValues() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $mock->setData(['operator' => Column::OPERATOR_ISNOTNULL]); + + $this->assertEquals([ + 'operator' => Column::OPERATOR_ISNOTNULL, + ], $mock->getData()); + } + + public function testGetEmptyDataIfOperatorNotNotNullOrNullNoFromToValues() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $operators = array_flip(Column::getAvailableOperators()); + unset($operators[Column::OPERATOR_ISNOTNULL]); + unset($operators[Column::OPERATOR_ISNULL]); + + foreach (array_keys($operators) as $operator) { + $mock->setData(['operator' => $operator]); + + $this->assertEmpty($mock->getData()); + } + } + + public function testGetNullData() + { + $mock = $this->getMockForAbstractClass(Column::class); + + try { + $mock->getData(); + } catch (\Exception $exception) { + $this->fail($exception->getMessage()); + } + } + + public function testGetFiltersWithoutData() + { + $mock = $this->getMockForAbstractClass(Column::class); + + try { + $mock->getFilters('aSource'); + } catch (\Exception $exception) { + $this->fail($exception->getMessage()); + } + } + + public function testGetData() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $operators = array_flip(Column::getAvailableOperators()); + unset($operators[Column::OPERATOR_ISNOTNULL]); + unset($operators[Column::OPERATOR_ISNULL]); + + foreach (array_keys($operators) as $operator) { + $mock->setData(['operator' => $operator, 'from' => 'from', 'to' => 'to']); + + $this->assertEquals([ + 'operator' => $operator, + 'from' => 'from', + 'to' => 'to', + ], $mock->getData()); + } + } + + public function testQueryIsAlwaysValid() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $this->assertTrue($mock->isQueryValid('foo')); + } + + public function testSetVisibleForSource() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setVisibleForSource(true); + + $this->assertEquals(true, $mock->isVisibleForSource()); + } + + public function testIsVisibleForSource() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setVisibleForSource(true); + + $this->assertTrue($mock->isVisibleForSource()); + } + + public function testSetPrimary() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setPrimary(true); + + $this->assertEquals(true, $mock->isPrimary()); + } + + public function testIsPrimary() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setPrimary(true); + + $this->assertTrue($mock->isPrimary()); + } + + public function testItThrowsExceptionIfSetAnAlignNotAllowed() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $this->expectException(\InvalidArgumentException::class); + + $mock->setAlign('foo'); + } + + public function testSetAlign() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setAlign(Column::ALIGN_RIGHT); + + $this->assertEquals(Column::ALIGN_RIGHT, $mock->getAlign()); + } + + public function testGetAlign() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setAlign(Column::ALIGN_RIGHT); + + $this->assertEquals(Column::ALIGN_RIGHT, $mock->getAlign()); + } + + public function testSetInputType() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setInputType('string'); + + $this->assertEquals('string', $mock->getInputType()); + } + + public function testGetInputType() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setInputType('string'); + + $this->assertEquals('string', $mock->getInputType()); + } + + public function testSetField() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setField('foo'); + + $this->assertEquals('foo', $mock->getField()); + } + + public function testGetField() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setField('foo'); + + $this->assertEquals('foo', $mock->getField()); + } + + public function testSetRole() + { + $role = 'ROLE_USER'; + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setRole($role); + + $this->assertEquals($role, $mock->getRole()); + } + + public function testGetRole() + { + $role = 'ROLE_USER'; + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setRole($role); + + $this->assertEquals($role, $mock->getRole()); + } + + public function testSetFilterType() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setFilterType('TEXTBOX'); + + $this->assertEquals('textbox', $mock->getFilterType()); + } + + public function testGetFilterType() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setFilterType('TEXTBOX'); + + $this->assertEquals('textbox', $mock->getFilterType()); + } + + public function testSetDataJunction() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setDataJunction(Column::DATA_DISJUNCTION); + + $this->assertEquals(Column::DATA_DISJUNCTION, $mock->getDataJunction()); + } + + public function testGetDataJunction() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setDataJunction(Column::DATA_DISJUNCTION); + + $this->assertEquals(Column::DATA_DISJUNCTION, $mock->getDataJunction()); + } + + public function testItThrowsExceptionIfSetDefaultOperatorWithOperatorNotAllowed() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $this->expectException(\Exception::class); + + $mock->setDefaultOperator('foo'); + } + + public function testSetDefaultOperator() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setDefaultOperator(Column::OPERATOR_LTE); + + $this->assertEquals(Column::OPERATOR_LTE, $mock->getDefaultOperator()); + } + + public function testGetDefaultOperator() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setDefaultOperator(Column::OPERATOR_LTE); + + $this->assertEquals(Column::OPERATOR_LTE, $mock->getDefaultOperator()); + } + + public function testHasOperator() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $this->assertTrue($mock->hasOperator(Column::OPERATOR_LIKE)); + $this->assertFalse($mock->hasOperator('foo')); + } + + public function testSetOperatorsVisible() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setOperatorsVisible(false); + + $this->assertEquals(false, $mock->getOperatorsVisible()); + } + + public function testGetOperatorsVisible() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setOperatorsVisible(false); + + $this->assertFalse($mock->getOperatorsVisible()); + } + + public function testSetValues() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $values = [0 => 'foo', 1 => 'bar']; + $mock->setValues($values); + + $this->assertEquals($values, $mock->getValues()); + } + + public function testGetValues() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $values = [0 => 'foo', 1 => 'bar']; + $mock->setValues($values); + + $this->assertEquals($values, $mock->getValues()); + } + + public function testSetSelectFrom() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSelectFrom('source'); + + $this->assertEquals('source', $mock->getSelectFrom()); + } + + public function testGetSelectFrom() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSelectFrom('source'); + + $this->assertEquals('source', $mock->getSelectFrom()); + } + + public function testSetSelectMulti() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSelectMulti(true); + + $this->assertEquals(true, $mock->getSelectMulti()); + } + + public function testGetSelectMulti() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSelectMulti(true); + + $this->assertTrue($mock->getSelectMulti()); + } + + public function testSetSelectExpanded() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSelectExpanded(true); + + $this->assertEquals(true, $mock->getSelectExpanded()); + } + + public function testGetSelectExpanded() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSelectExpanded(true); + + $this->assertTrue($mock->getSelectExpanded()); + } + + public function testSetAuthChecker() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $mock->setAuthorizationChecker($authChecker); + + $this->assertEquals($authChecker, $mock->getAuthorizationChecker()); + } + + public function testNoParentType() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $this->assertEmpty($mock->getParentType()); + } + + public function testNoType() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $this->assertEmpty($mock->getType()); + } + + public function testIsFilterSubmitOnChange() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSelectMulti(true); + + $this->assertFalse($mock->isFilterSubmitOnChange()); + + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSelectMulti(false); + + $this->assertTrue($mock->isFilterSubmitOnChange()); + } + + public function testSetSearchOnClick() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSearchOnClick(false); + + $this->assertEquals(false, $mock->getSearchOnClick()); + } + + public function testGetSearchOnClick() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSearchOnClick(false); + + $this->assertFalse($mock->getSearchOnClick()); + } + + public function testSetSafe() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSafe('html'); + + $this->assertEquals('html', $mock->getSafe()); + } + + public function testGetSafe() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSafe('html'); + + $this->assertEquals('html', $mock->getSafe()); + } + + public function testSetSeparator() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSeparator(';'); + + $this->assertEquals(';', $mock->getSeparator()); + } + + public function testGetSeparator() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSeparator(';'); + + $this->assertEquals(';', $mock->getSeparator()); + } + + public function testSetJoinType() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setJoinType('left'); + + $this->assertEquals('left', $mock->getJoinType()); + } + + public function testGetJoinType() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setJoinType('left'); + + $this->assertEquals('left', $mock->getJoinType()); + } + + public function testSetExport() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setExport(true); + + $this->assertEquals(true, $mock->getExport()); + } + + public function testGetExport() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setExport(true); + + $this->assertTrue($mock->getExport()); + } + + public function testSetClass() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setClass('aClass'); + + $this->assertEquals('aClass', $mock->getClass()); + } + + public function testGetClass() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setClass('aClass'); + + $this->assertEquals('aClass', $mock->getClass()); + } + + public function testSetIsManualField() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setIsManualField(true); + + $this->assertEquals(true, $mock->getIsManualField()); + } + + public function testGetIsManualField() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setIsManualField(true); + + $this->assertTrue($mock->getIsManualField()); + } + + public function testSetIsAggregate() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setIsAggregate(true); + + $this->assertEquals(true, $mock->getIsAggregate()); + } + + public function testGetIsAggregate() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setIsAggregate(true); + + $this->assertTrue($mock->getIsAggregate()); + } + + public function testSetUsePrefixTitle() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setUsePrefixTitle(false); + + $this->assertEquals(false, $mock->getUsePrefixTitle()); + } + + public function testGetUsePrefixTitle() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setUsePrefixTitle(false); + + $this->assertFalse($mock->getUsePrefixTitle()); + } + + public function testSetTranslationDomain() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setTranslationDomain('it'); + + $this->assertEquals('it', $mock->getTranslationDomain()); + } + + public function testGetTranslationDomain() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setTranslationDomain('it'); + + $this->assertEquals('it', $mock->getTranslationDomain()); + } + + public function testGetFiltersWithoutOperator() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $this->assertEmpty($mock->getFilters('aSource')); + } + + public function testGetFiltersBtwWithoutFrom() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_BTW, 'to' => 10]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_LT, 10), + ], $mock->getFilters('aSource')); + } + + public function testGetFiltersBtwWithoutTo() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_BTW, 'from' => 1]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_GT, 1), + ], $mock->getFilters('aSource')); + } + + public function testGetFiltersBtw() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_BTW, 'from' => 1, 'to' => 10]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_GT, 1), + new Filter(Column::OPERATOR_LT, 10), + ], $mock->getFilters('aSource')); + } + + public function testGetFiltersBtweWithoutFrom() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_BTWE, 'to' => 10]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_LTE, 10), + ], $mock->getFilters('aSource')); + } + + public function testGetFiltersBtweWithoutTo() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_BTWE, 'from' => 1]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_GTE, 1), + ], $mock->getFilters('aSource')); + } + + public function testGetFiltersBtwe() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => Column::OPERATOR_BTWE, 'from' => 1, 'to' => 10]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_GTE, 1), + new Filter(Column::OPERATOR_LTE, 10), + ], $mock->getFilters('aSource')); + } + + public function testGetFiltersNullNoNull() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $mock->setData(['operator' => Column::OPERATOR_ISNULL]); + $this->assertEquals([new Filter(Column::OPERATOR_ISNULL)], $mock->getFilters('aSource')); + + $mock->setData(['operator' => Column::OPERATOR_ISNOTNULL]); + $this->assertEquals([new Filter(Column::OPERATOR_ISNOTNULL)], $mock->getFilters('aSource')); + } + + public function testGetFiltersLikeCombinationsNoMulti() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $operators = [ + Column::OPERATOR_LIKE, + Column::OPERATOR_RLIKE, + Column::OPERATOR_LLIKE, + Column::OPERATOR_SLIKE, + Column::OPERATOR_RSLIKE, + Column::OPERATOR_LSLIKE, + Column::OPERATOR_EQ, + ]; + + foreach ($operators as $operator) { + $mock->setData(['operator' => $operator]); + $this->assertEmpty($mock->getFilters('aSource')); + $this->assertEquals(Column::DATA_CONJUNCTION, $mock->getDataJunction()); + } + } + + public function testGetFiltersLikeCombinationsMulti() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setSelectMulti(true); + + $operators = [ + Column::OPERATOR_LIKE, + Column::OPERATOR_RLIKE, + Column::OPERATOR_LLIKE, + Column::OPERATOR_SLIKE, + Column::OPERATOR_RSLIKE, + Column::OPERATOR_LSLIKE, + Column::OPERATOR_EQ, + ]; + + foreach ($operators as $operator) { + $mock->setData(['operator' => $operator]); + $this->assertEmpty($mock->getFilters('aSource')); + $this->assertEquals(Column::DATA_DISJUNCTION, $mock->getDataJunction()); + } + } + + public function testGetFiltersNotLikeCombination() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $operators = [ + Column::OPERATOR_NEQ, + Column::OPERATOR_NLIKE, + Column::OPERATOR_NSLIKE, + ]; + + foreach ($operators as $operator) { + $mock->setData(['operator' => $operator, 'from' => [1, 2, 3]]); + $this->assertEquals([ + new Filter($operator, 1), + new Filter($operator, 2), + new Filter($operator, 3), + ], $mock->getFilters('aSource')); + } + } + + public function testGetFiltersWithNotHandledOperator() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setData(['operator' => 'foo', 'from' => 'bar']); + + $this->assertEquals([ + new Filter(Column::OPERATOR_LIKE, 'bar'), + ], $mock->getFilters('aSource')); + } + + public function testSetOperators() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setOperators([ + Column::OPERATOR_ISNULL, + Column::OPERATOR_ISNOTNULL, + ]); + + $this->assertEquals([ + Column::OPERATOR_ISNULL, + Column::OPERATOR_ISNOTNULL, + ], $mock->getOperators()); + } + + public function testGetOperators() + { + $mock = $this->getMockForAbstractClass(Column::class); + + $this->assertEquals(Column::getAvailableOperators(), $mock->getOperators()); + } + + public function testItHasDqlFunctionWithoutMatchesResultArray() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setField('foo:bar:foobar'); + + $this->assertEquals(1, $mock->hasDQLFunction()); + } + + public function testItHasDqlFunctionWithMatchesResultArray() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setField('foo:bar:foobar'); + + $result = []; + $this->assertEquals(1, $mock->hasDQLFunction($result)); + $this->assertEquals([ + 0 => 'foo:bar:foobar', + 'all' => 'foo:bar:foobar', + 1 => 'foo:bar:foobar', + 'field' => 'foo', + 2 => 'foo', + 'function' => 'bar', + 3 => 'bar', + 4 => ':', + 'parameters' => 'foobar', + 5 => 'foobar', + ], $result); + } + + public function testItHasNotDqlFunctionWithoutMatchesResultArray() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setField('foo'); + + $this->assertEquals(0, $mock->hasDQLFunction()); + } + + public function testItHasNotDqlFunctionWithMatchesResultArray() + { + $mock = $this->getMockForAbstractClass(Column::class); + $mock->setField('foo'); + + $result = []; + $this->assertEquals(0, $mock->hasDQLFunction($result)); + $this->assertEmpty($result); + } +} diff --git a/Tests/Grid/Column/DateColumnTest.php b/Tests/Grid/Column/DateColumnTest.php new file mode 100644 index 00000000..ebb77f54 --- /dev/null +++ b/Tests/Grid/Column/DateColumnTest.php @@ -0,0 +1,125 @@ +assertEquals('date', $this->column->getType()); + } + + public function testGetFiltersWithoutValue() + { + $operators = array_flip(Column::getAvailableOperators()); + unset($operators[Column::OPERATOR_ISNOTNULL]); + unset($operators[Column::OPERATOR_ISNULL]); + + foreach (array_keys($operators) as $operator) { + $this->column->setData(['operator' => $operator]); + $this->assertEmpty($this->column->getFilters('asource')); + } + } + + public function testGetFiltersWithNotNullOperator() + { + $this->column->setData(['operator' => Column::OPERATOR_ISNOTNULL]); + + $this->assertEquals([new Filter(Column::OPERATOR_ISNOTNULL)], $this->column->getFilters('asource')); + } + + public function testGetFiltersWithIsNullOperator() + { + $this->column->setData(['operator' => Column::OPERATOR_ISNULL]); + $filters = $this->column->getFilters('asource'); + + $this->assertEquals([new Filter(Column::OPERATOR_ISNULL)], $filters); + } + + public function testGetFiltersOperatorEq() + { + $from = '2017-03-18'; + $to = '2017-03-20'; + + $this->column->setData(['operator' => Column::OPERATOR_EQ, 'from' => $from, 'to' => $to]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_GTE, new \DateTime($from . ' 00:00:00')), + new Filter(Column::OPERATOR_LTE, new \DateTime($from . '23:59:59')), + ], $this->column->getFilters('asource')); + } + + public function testGetFiltersOperatorNeq() + { + $from = '2017-03-18'; + $to = '2017-03-20'; + + $this->column->setData(['operator' => Column::OPERATOR_NEQ, 'from' => $from, 'to' => $to]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_LT, new \DateTime($from . ' 00:00:00')), + new Filter(Column::OPERATOR_GT, new \DateTime($from . '23:59:59')), + ], $this->column->getFilters('asource')); + $this->assertEquals(Column::DATA_DISJUNCTION, $this->column->getDataJunction()); + } + + public function testGetFiltersOperatorLt() + { + $value = '2017-03-18'; + + $this->column->setData(['operator' => Column::OPERATOR_LT, 'from' => $value]); + + $this->assertEquals( + [new Filter(Column::OPERATOR_LT, new \DateTime($value . '00:00:00'))], + $this->column->getFilters('asource') + ); + } + + public function testGetFiltersOperatorGte() + { + $value = '2017-03-18'; + + $this->column->setData(['operator' => Column::OPERATOR_GTE, 'from' => $value]); + + $this->assertEquals( + [new Filter(Column::OPERATOR_GTE, new \DateTime($value . '00:00:00'))], + $this->column->getFilters('asource') + ); + } + + public function testGetFiltersOperatorGt() + { + $value = '2017-03-18'; + + $this->column->setData(['operator' => Column::OPERATOR_GT, 'from' => $value]); + + $this->assertEquals( + [new Filter(Column::OPERATOR_GT, new \DateTime($value . '23:59:59'))], + $this->column->getFilters('asource') + ); + } + + public function testGetFiltersOperatorLte() + { + $value = '2017-03-18'; + + $this->column->setData(['operator' => Column::OPERATOR_LTE, 'from' => $value]); + + $this->assertEquals( + [new Filter(Column::OPERATOR_LTE, new \DateTime($value . '23:59:59'))], + $this->column->getFilters('asource') + ); + } + + public function setUp(): void + { + $this->column = new DateColumn(); + } +} diff --git a/Tests/Grid/Column/DateTimeColumnTest.php b/Tests/Grid/Column/DateTimeColumnTest.php new file mode 100644 index 00000000..d5d8fc59 --- /dev/null +++ b/Tests/Grid/Column/DateTimeColumnTest.php @@ -0,0 +1,302 @@ +assertEquals('datetime', $column->getType()); + } + + public function testSetFormat() + { + $format = 'Y-m-d'; + + $column = new DateTimeColumn(); + $column->setFormat($format); + + $this->assertEquals($format, $column->getFormat()); + } + + public function testGetFormat() + { + $format = 'Y-m-d'; + + $column = new DateTimeColumn(); + $column->setFormat($format); + + $this->assertEquals($format, $column->getFormat()); + } + + public function testSetInputFormat() + { + $inputFormat = 'Y-m-d'; + + $column = new DateTimeColumn(); + $column->setInputFormat($inputFormat); + + $this->assertEquals($inputFormat, $column->getInputFormat()); + } + + public function testGetInputFormat() + { + $inputFormat = 'Y-m-d'; + + $column = new DateTimeColumn(); + $column->setInputFormat($inputFormat); + + $this->assertEquals($inputFormat, $column->getInputFormat()); + } + + public function testSetTimezone() + { + $timezone = 'UTC'; + + $column = new DateTimeColumn(); + $column->setTimezone($timezone); + + $this->assertEquals($timezone, $column->getTimezone()); + } + + public function testGetTimezone() + { + $timezone = 'UTC'; + + $column = new DateTimeColumn(); + $column->setTimezone($timezone); + + $this->assertEquals($timezone, $column->getTimezone()); + } + + public function testRenderCellWithoutCallback() + { + $column = new DateTimeColumn(); + $column->setFormat('Y-m-d H:i:s'); + + $dateTime = '2000-01-01 01:00:00'; + $now = new \DateTime($dateTime); + + $this->assertEquals( + $dateTime, + $column->renderCell( + $now, + $this->createMock(Row::class), + $this->createMock(Router::class) + ) + ); + } + + public function testRenderCellWithCallback() + { + $column = new DateTimeColumn(); + $column->setFormat('Y-m-d H:i:s'); + $column->manipulateRenderCell(fn($value, $row, $router) => '01:00:00'); + + $dateTime = '2000-01-01 01:00:00'; + $now = new \DateTime($dateTime); + + $this->assertEquals( + '01:00:00', + $column->renderCell( + $now, + $this->createMock(Row::class), + $this->createMock(Router::class) + ) + ); + } + + public function testFilterWithValue() + { + $column = new DateTimeColumn(); + $column->setData(['operator' => Column::OPERATOR_BTW, 'from' => '2017-03-22 01:30:00', 'to' => '2017-03-23 19:00:00']); + + $this->assertEquals([ + new Filter(Column::OPERATOR_GT, new \DateTime('2017-03-22 01:30:00')), + new Filter(Column::OPERATOR_LT, new \DateTime('2017-03-23 19:00:00')), + ], $column->getFilters('asource')); + } + + public function testFilterWithFormattedValue() + { + $column = new DateTimeColumn(); + $column->setInputFormat('m/d/Y H-i-s'); + $column->setData(['operator' => Column::OPERATOR_BTW, 'from' => '03/22/2017 01-30-00', 'to' => '03/23/2017 19-00-00']); + + $this->assertEquals([ + new Filter(Column::OPERATOR_GT, new \DateTime('2017-03-22 01:30:00')), + new Filter(Column::OPERATOR_LT, new \DateTime('2017-03-23 19:00:00')), + ], $column->getFilters('asource')); + } + + public function testFilterWithoutValue() + { + $column = new DateTimeColumn(); + $column->setData(['operator' => Column::OPERATOR_ISNULL]); + + $this->assertEquals([new Filter(Column::OPERATOR_ISNULL)], $column->getFilters('asource')); + } + + public function testQueryIsValid() + { + $column = new DateTimeColumn(); + + $this->assertTrue($column->isQueryValid('2017-03-22 23:00:00')); + } + + public function testQueryIsInvalid() + { + $column = new DateTimeColumn(); + + $this->assertFalse($column->isQueryValid('foo')); + } + + public function testInputFormattedQueryIsValid() + { + $column = new DateTimeColumn(); + $column->setInputFormat('m/d/Y H-i-s'); + + $this->assertTrue($column->isQueryValid('03/22/2017 23-00-00')); + } + + public function testInputFormattedQueryIsInvalid() + { + $column = new DateTimeColumn(); + $column->setInputFormat('m/d/Y H-i-s'); + + $this->assertFalse($column->isQueryValid('2017-03-22 23:00:00')); + } + + public function testInitializeDefaultParams() + { + $column = new DateTimeColumn(); + + $this->assertEquals(null, $column->getFormat()); + $this->assertEquals('Y-m-d H:i:s', $column->getInputFormat()); + $this->assertEquals([ + Column::OPERATOR_EQ, + Column::OPERATOR_NEQ, + Column::OPERATOR_LT, + Column::OPERATOR_LTE, + Column::OPERATOR_GT, + Column::OPERATOR_GTE, + Column::OPERATOR_BTW, + Column::OPERATOR_BTWE, + Column::OPERATOR_ISNULL, + Column::OPERATOR_ISNOTNULL, + ], $column->getOperators()); + $this->assertEquals(Column::OPERATOR_EQ, $column->getDefaultOperator()); + $this->assertEquals(date_default_timezone_get(), $column->getTimezone()); + } + + public function testInitialize() + { + $format = 'Y-m-d H:i:s'; + $inputFormat = 'Y-m-d H:i:s'; + $timezone = 'UTC'; + + $params = [ + 'format' => $format, + 'inputFormat' => $inputFormat, + 'operators' => [Column::OPERATOR_LT, Column::OPERATOR_LTE], + 'defaultOperator' => Column::OPERATOR_LT, + 'timezone' => $timezone, + ]; + + $column = new DateTimeColumn($params); + + $this->assertEquals($format, $column->getFormat()); + $this->assertEquals($inputFormat, $column->getInputFormat()); + $this->assertEquals([ + Column::OPERATOR_LT, Column::OPERATOR_LTE, + ], $column->getOperators()); + $this->assertEquals(Column::OPERATOR_LT, $column->getDefaultOperator()); + $this->assertEquals($timezone, $column->getTimezone()); + } + + /** + * @dataProvider provideDisplayInput + */ + public function testCorrectDisplayOut($value, $expectedOutput, $timeZone = null) + { + $column = new DateTimeColumn(); + $column->setFormat('Y-m-d H:i:s'); + + if ($timeZone !== null) { + $column->setTimezone($timeZone); + } + + $this->assertEquals($expectedOutput, $column->getDisplayedValue($value)); + } + + public function testDisplayValueForDateTimeImmutable() + { + if (PHP_VERSION_ID < 50500) { + $this->markTestSkipped('\\DateTimeImmutable was introduced in PHP 5.5'); + } + + $now = new \DateTimeImmutable(); + + $column = new DateTimeColumn(); + $column->setFormat('Y-m-d H:i:s'); + $this->assertEquals($now->format('Y-m-d H:i:s'), $column->getDisplayedValue($now)); + } + + public function testDateTimeZoneForDisplayValueIsTheSameAsTheColumn() + { + $column = new DateTimeColumn(); + $column->setFormat('Y-m-d H:i:s'); + $column->setTimezone('UTC'); + + $now = new \DateTime('2000-01-01 01:00:00', new \DateTimeZone('Europe/Amsterdam')); + + $this->assertEquals('2000-01-01 00:00:00', $column->getDisplayedValue($now)); + } + +// public function testDisplayValueWithDefaultFormats() +// { +// $column = new DateTimeColumn(); +// $now = new \DateTime('2017-03-22 22:52:00'); +// +// $this->assertEquals('Mar 22, 2017, 10:52:00 PM', $column->getDisplayedValue($now)); +// } +// +// public function testDisplayValueWithoutFormatButTimeZone() +// { +// $column = new DateTimeColumn(); +// $column->setTimezone('UTC'); +// +// $now = new \DateTime('2017-03-22 22:52:00', new \DateTimeZone('Europe/Amsterdam')); +// +// $this->assertEquals('Mar 22, 2017, 9:52:00 PM', $column->getDisplayedValue($now)); +// } +// +// public function testDisplayValueWithFallbackFormat() +// { +// $column = new DateTimeColumn(); +// $column->setTimezone(\IntlDateFormatter::NONE); +// +// $now = new \DateTime('2017/03/22 22:52:00'); +// +// $this->assertEquals('2017-03-22 20:52:00', $column->getDisplayedValue($now)); +// } + + public function provideDisplayInput() + { + $now = new \DateTime(); + + return [ + [$now, $now->format('Y-m-d H:i:s')], + ['2016/01/01 12:13:14', '2016-01-01 12:13:14'], + [1, '1970-01-01 00:00:01', 'UTC'], + ['', ''], + ]; + } +} diff --git a/Tests/Grid/Column/JoinColumnTest.php b/Tests/Grid/Column/JoinColumnTest.php new file mode 100644 index 00000000..241cba7f --- /dev/null +++ b/Tests/Grid/Column/JoinColumnTest.php @@ -0,0 +1,94 @@ +assertEquals('join', $this->column->getType()); + } + + public function testInitializeDefaultParams() + { + $params = []; + $column = new JoinColumn($params); + + $this->assertEquals([], $column->getParams()); + $this->assertEquals([], $column->getJoinColumns()); + $this->assertEquals(' ', $column->getSeparator()); + $this->assertEquals(true, $column->isVisibleForSource()); + $this->assertEquals(true, $column->getIsManualField()); + } + + public function testInitialize() + { + $col1 = 'col1'; + $col2 = 'col2'; + $separator = '/'; + + $params = [ + 'columns' => [$col1, $col2], + 'separator' => $separator, + ]; + $column = new JoinColumn($params); + + $this->assertEquals($params, $column->getParams()); + $this->assertEquals([$col1, $col2], $column->getJoinColumns()); + $this->assertEquals($separator, $column->getSeparator()); + } + + public function testSetJoinColumns() + { + $col1 = 'col1'; + $col2 = 'col2'; + + $this->column->setJoinColumns([$col1, $col2]); + + $this->assertEquals([$col1, $col2], $this->column->getJoinColumns()); + } + + public function testGetjoinColumns() + { + $col1 = 'col1'; + $col2 = 'col2'; + + $this->column->setJoinColumns([$col1, $col2]); + + $this->assertEquals([$col1, $col2], $this->column->getJoinColumns()); + } + + public function testSetColumnNameOnFilters() + { + $col1 = 'col1'; + $col2 = 'col2'; + $separator = '/'; + + $params = [ + 'columns' => [$col1, $col2], + 'separator' => $separator, + ]; + + $column = new JoinColumn($params); + $column->setData(['operator' => Column::OPERATOR_ISNOTNULL]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_ISNOTNULL, null, $col1), + new Filter(Column::OPERATOR_NEQ, null, $col1), + new Filter(Column::OPERATOR_ISNOTNULL, null, $col2), + new Filter(Column::OPERATOR_NEQ, null, $col2), + ], $column->getFilters('asource')); + } + + public function setUp(): void + { + $this->column = new JoinColumn(); + } +} diff --git a/Tests/Grid/Column/MassActionColumnTest.php b/Tests/Grid/Column/MassActionColumnTest.php new file mode 100644 index 00000000..040a0fea --- /dev/null +++ b/Tests/Grid/Column/MassActionColumnTest.php @@ -0,0 +1,46 @@ +assertEquals('massaction', $this->column->getType()); + } + + public function testGetFilterType() + { + $this->assertEquals('massaction', $this->column->getFilterType()); + } + + public function testIsVisible() + { + $this->assertFalse($this->column->isVisible(true)); + $this->assertTrue($this->column->isVisible(false)); + } + + public function testInitialize() + { + $this->assertEquals([ + 'id' => MassActionColumn::ID, + 'title' => '', + 'size' => 15, + 'filterable' => true, + 'sortable' => false, + 'source' => false, + 'align' => Column::ALIGN_CENTER, + ], $this->column->getParams()); + } + + public function setUp(): void + { + $this->column = new MassActionColumn(); + } +} diff --git a/Tests/Grid/Column/NumberColumnTest.php b/Tests/Grid/Column/NumberColumnTest.php new file mode 100644 index 00000000..f1e29cd0 --- /dev/null +++ b/Tests/Grid/Column/NumberColumnTest.php @@ -0,0 +1,309 @@ +assertEquals('number', $this->column->getType()); + } + + public function testInitializeDefaultParams() + { + $this->assertEquals(Column::ALIGN_RIGHT, $this->column->getAlign()); + $this->assertEquals(\NumberFormatter::DECIMAL, $this->column->getStyle()); + $this->assertEquals(\Locale::getDefault(), $this->column->getLocale()); + $this->assertEquals(null, $this->column->getPrecision()); + $this->assertEquals(false, $this->column->getGrouping()); + $this->assertEquals(\NumberFormatter::ROUND_HALFUP, $this->column->getRoundingMode()); + $this->assertEquals(null, $this->column->getRuleSet()); + $this->assertEquals(null, $this->column->getCurrencyCode()); + $this->assertEquals(false, $this->column->getFractional()); + $this->assertEquals(null, $this->column->getMaxFractionDigits()); + $this->assertEquals([ + Column::OPERATOR_EQ, + Column::OPERATOR_NEQ, + Column::OPERATOR_LT, + Column::OPERATOR_LTE, + Column::OPERATOR_GT, + Column::OPERATOR_GTE, + Column::OPERATOR_BTW, + Column::OPERATOR_BTWE, + Column::OPERATOR_ISNULL, + Column::OPERATOR_ISNOTNULL, + ], $this->column->getOperators()); + $this->assertEquals(Column::OPERATOR_EQ, $this->column->getDefaultOperator()); + } + + public function testInitializeStyle() + { + $column = new NumberColumn(['style' => 'decimal']); + $this->assertEquals(\NumberFormatter::DECIMAL, $column->getStyle()); + + $column = new NumberColumn(['style' => 'percent']); + $this->assertEquals(\NumberFormatter::PERCENT, $column->getStyle()); + + $column = new NumberColumn(['style' => 'money']); + $this->assertEquals(\NumberFormatter::CURRENCY, $column->getStyle()); + + $column = new NumberColumn(['style' => 'currency']); + $this->assertEquals(\NumberFormatter::CURRENCY, $column->getStyle()); + + $column = new NumberColumn(['style' => 'duration']); + $this->assertEquals(\NumberFormatter::DURATION, $column->getStyle()); + $this->assertEquals('en', $column->getLocale()); + $this->assertEquals('%in-numerals', $column->getRuleSet()); + + $column = new NumberColumn(['style' => 'scientific']); + $this->assertEquals(\NumberFormatter::SCIENTIFIC, $column->getStyle()); + + $column = new NumberColumn(['style' => 'spellout']); + $this->assertEquals(\NumberFormatter::SPELLOUT, $column->getStyle()); + } + + public function testInitializeStyleWithInvalidValue() + { + $this->expectException(\InvalidArgumentException::class); + $column = new NumberColumn(['style' => 'foostyle']); + } + + public function testInitializeLocale() + { + $column = new NumberColumn(['locale' => 'it']); + $this->assertEquals('it', $column->getLocale()); + } + + public function testInitializePrecision() + { + $column = new NumberColumn(['precision' => 2]); + $this->assertEquals(2, $column->getPrecision()); + } + + public function testInitializeGrouping() + { + $column = new NumberColumn(['grouping' => 3]); + $this->assertEquals(3, $column->getGrouping()); + } + + public function testInitializeRoundingMode() + { + $column = new NumberColumn(['roundingMode' => \NumberFormatter::ROUND_HALFDOWN]); + $this->assertEquals(\NumberFormatter::ROUND_HALFDOWN, $column->getRoundingMode()); + } + + public function testInitializeRuleSet() + { + $column = new NumberColumn(['ruleSet' => \NumberFormatter::PUBLIC_RULESETS]); + $this->assertEquals(\NumberFormatter::PUBLIC_RULESETS, $column->getRuleSet()); + } + + public function testInitializeCurrencyCode() + { + $column = new NumberColumn(['currencyCode' => 'EUR']); + $this->assertEquals('EUR', $column->getCurrencyCode()); + } + + public function testInizializeFractional() + { + $column = new NumberColumn(['fractional' => true]); + $this->assertEquals(true, $column->getFractional()); + } + + public function testInizializeMaxFractionalDigits() + { + $column = new NumberColumn(['maxFractionDigits' => 2]); + $this->assertEquals(2, $column->getMaxFractionDigits()); + } + + public function testIsQueryValid() + { + $this->assertTrue($this->column->isQueryValid('1')); + $this->assertTrue($this->column->isQueryValid(1)); + $this->assertTrue($this->column->isQueryValid('1.2')); + $this->assertTrue($this->column->isQueryValid(1.2)); + $this->assertTrue($this->column->isQueryValid([1, '1', 1.2, '1.2', 'foo'])); + $this->assertFalse($this->column->isQueryValid('foo')); + $this->assertFalse($this->column->isQueryValid(['foo', 'bar'])); + } + + public function testRenderCellWithCallback() + { + $value = 1.0; + $this->column->manipulateRenderCell(fn($value, $row, $router) => (int) $value); + + $result = $this->column->renderCell( + $value, + $this->createMock(Row::class), + $this->createMock(Router::class) + ); + + $this->assertEquals($result, $value); + } + + public function testDisplayedValueWithEmptyValue() + { + $this->assertEquals('', $this->column->getDisplayedValue('')); + $this->assertEquals('', $this->column->getDisplayedValue(null)); + } + + public function testDisplayedPercentValue() + { + $column = new NumberColumn([ + 'precision' => 2, + 'roundingMode' => \NumberFormatter::ROUND_DOWN, + 'ruleSet' => \NumberFormatter::POSITIVE_PREFIX, + 'maxFractionDigits' => 2, + 'grouping' => 3, + 'style' => 'percent', + 'locale' => 'en_US', + ]); + + $this->assertEquals('1,000.00%', $column->getDisplayedValue(1000)); + } + + public function testDisplayedCurrencyValue() + { + $column = new NumberColumn([ + 'precision' => 2, + 'roundingMode' => \NumberFormatter::ROUND_DOWN, + 'ruleSet' => \NumberFormatter::POSITIVE_PREFIX, + 'maxFractionDigits' => 2, + 'grouping' => 3, + 'style' => 'currency', + 'currencyCode' => 'EUR', + 'locale' => 'en_US', + ]); + + $this->assertEquals('€1,000.00', $column->getDisplayedValue(1000)); + } + + public function testDisplayedCurrencyWithoutCurrencyCode() + { + $column = new NumberColumn([ + 'precision' => 2, + 'roundingMode' => \NumberFormatter::ROUND_DOWN, + 'ruleSet' => \NumberFormatter::POSITIVE_PREFIX, + 'maxFractionDigits' => 2, + 'grouping' => 3, + 'style' => 'currency', + 'locale' => 'en_US', + ]); + + $this->assertEquals('$1,000.00', $column->getDisplayedValue(1000)); + } + + public function testDisplayedCurrencyWithoutAValidISO4217CCurrencyCode() + { + $column = new NumberColumn([ + 'precision' => 2, + 'roundingMode' => \NumberFormatter::ROUND_DOWN, + 'ruleSet' => \NumberFormatter::POSITIVE_PREFIX, + 'maxFractionDigits' => 2, + 'grouping' => 3, + 'style' => 'currency', + 'currencyCode' => 'notAnISO4217C', + ]); + + $this->expectException(\Exception::class); + $column->getDisplayedValue(1000); + } + + public function testDisplayedValueFromArrayValues() + { + $column = new NumberColumn([ + 'style' => 'decimal', + 'values' => [100 => 200], + ]); + + $this->assertEquals(200, $column->getDisplayedValue(100)); + } + + public function testGetFilters() + { + $this->column->setData(['operator' => Column::OPERATOR_BTW, 'from' => '10', 'to' => '20']); + $this->assertEquals([ + new Filter(Column::OPERATOR_GT, 10), + new Filter(Column::OPERATOR_LT, 20), + ], $this->column->getFilters('asource')); + + $this->column->setData(['operator' => Column::OPERATOR_BTW, 'from' => 10, 'to' => 20]); + $this->assertEquals([ + new Filter(Column::OPERATOR_GT, 10), + new Filter(Column::OPERATOR_LT, 20), + ], $this->column->getFilters('asource')); + + $this->column->setData(['operator' => Column::OPERATOR_ISNULL]); + $this->assertEquals([ + new Filter(Column::OPERATOR_ISNULL), + ], $this->column->getFilters('asource')); + } + + public function getStyle() + { + $column = new NumberColumn(['style' => 'decimal']); + $this->assertEquals(\NumberFormatter::DECIMAL, $column->getStyle()); + } + + public function getLocale() + { + $column = new NumberColumn(['locale' => 'it_IT']); + $this->assertEquals('it_IT', $column->getLocale()); + } + + public function getPrecision() + { + $column = new NumberColumn(['precision' => 2]); + $this->assertEquals(2, $column->getPrecision()); + } + + public function getGrouping() + { + $column = new NumberColumn(['grouping' => 3]); + $this->assertEquals(3, $column->getGrouping()); + } + + public function getRoundingMode() + { + $column = new NumberColumn(['roundingMode' => \NumberFormatter::ROUND_HALFDOWN]); + $this->assertEquals(\NumberFormatter::ROUND_HALFDOWN, $column->getRoundingMode()); + } + + public function getRuleSet() + { + $column = new NumberColumn(['ruleSet' => \NumberFormatter::PUBLIC_RULESETS]); + $this->assertEquals(\NumberFormatter::PUBLIC_RULESETS, $column->getRuleSet()); + } + + public function getCurrencyCode() + { + $column = new NumberColumn(['currencyCode' => 'USD']); + $this->assertEquals('USD', $column->getCurrencyCode()); + } + + public function getFractional() + { + $column = new NumberColumn(['fractional' => true]); + $this->assertTrue($column->getFractional()); + } + + public function getMaxFractionDigits() + { + $column = new NumberColumn(['maxFractionDigits' => 3]); + $this->assertEquals(3, $column->getMaxFractionDigits()); + } + + public function setUp(): void + { + $this->column = new NumberColumn(); + } +} diff --git a/Tests/Grid/Column/RankColumnTest.php b/Tests/Grid/Column/RankColumnTest.php new file mode 100644 index 00000000..fc4bfcd4 --- /dev/null +++ b/Tests/Grid/Column/RankColumnTest.php @@ -0,0 +1,87 @@ +assertEquals('rank', $this->column->getType()); + } + + public function testInitialize() + { + $params = [ + 'foo' => 'foo', + 'bar' => 'bar', + 'title' => 'title', + 'filterable' => true, + 'source' => true, + ]; + + $column = new RankColumn($params); + + $this->assertEquals([ + 'foo' => 'foo', + 'bar' => 'bar', + 'title' => 'title', + 'filterable' => false, + 'sortable' => false, + 'source' => false, + ], $column->getParams()); + } + + public function testSetId() + { + $this->assertEquals('rank', $this->column->getId()); + + $column = new RankColumn(['id' => 'foo']); + $this->assertEquals('foo', $column->getId()); + } + + public function testSetTitle() + { + $this->assertEquals('rank', $this->column->getTitle()); + + $column = new RankColumn(['title' => 'foo']); + $this->assertEquals('foo', $column->getTitle()); + } + + public function testSetSize() + { + $this->assertEquals('30', $this->column->getSize()); + + $column = new RankColumn(['size' => '20']); + $this->assertEquals('20', $column->getSize()); + } + + public function testSetAlign() + { + $this->assertEquals(Column::ALIGN_CENTER, $this->column->getAlign()); + + $column = new RankColumn(['align' => Column::ALIGN_RIGHT]); + $this->assertEquals(Column::ALIGN_RIGHT, $column->getAlign()); + } + + public function testRenderCell() + { + $this->assertEquals(1, $this->column->renderCell(true, $this->createMock(Row::class), $this->createMock(Router::class))); + $this->assertEquals(2, $this->column->getRank()); + + $this->assertEquals(2, $this->column->renderCell(true, $this->createMock(Row::class), $this->createMock(Router::class))); + $this->assertEquals(3, $this->column->getRank()); + } + + public function setUp(): void + { + $this->column = new RankColumn(); + } +} diff --git a/Tests/Grid/Column/SimpleArrayColumnTest.php b/Tests/Grid/Column/SimpleArrayColumnTest.php new file mode 100644 index 00000000..8714eea6 --- /dev/null +++ b/Tests/Grid/Column/SimpleArrayColumnTest.php @@ -0,0 +1,123 @@ +assertEquals('simple_array', $this->column->getType()); + } + + public function setUp(): void + { + $this->column = new SimpleArrayColumn(); + } + + public function testInitializeDefaultParams() + { + $this->assertEquals([ + Column::OPERATOR_LIKE, + Column::OPERATOR_NLIKE, + Column::OPERATOR_EQ, + Column::OPERATOR_NEQ, + Column::OPERATOR_ISNULL, + Column::OPERATOR_ISNOTNULL, + ], $this->column->getOperators()); + + $this->assertEquals(Column::OPERATOR_LIKE, $this->column->getDefaultOperator()); + } + + public function testEqualFilter() + { + $value = ['foo, bar']; + + $this->column->setData(['operator' => Column::OPERATOR_EQ, 'from' => $value]); + + $this->assertEquals([new Filter(Column::OPERATOR_EQ, 'foo, bar')], $this->column->getFilters('asource')); + } + + public function testNotEqualFilter() + { + $value = ['foo, bar']; + + $this->column->setData(['operator' => Column::OPERATOR_NEQ, 'from' => $value]); + + $this->assertEquals([new Filter(Column::OPERATOR_NEQ, 'foo, bar')], $this->column->getFilters('asource')); + } + + public function testLikeFilter() + { + $value = ['foo, bar']; + + $this->column->setData(['operator' => Column::OPERATOR_LIKE, 'from' => $value]); + + $this->assertEquals([new Filter(Column::OPERATOR_LIKE, 'foo, bar')], $this->column->getFilters('asource')); + } + + public function testNotLikeFilter() + { + $value = ['foo, bar']; + + $this->column->setData(['operator' => Column::OPERATOR_NLIKE, 'from' => $value]); + + $this->assertEquals([new Filter(Column::OPERATOR_NLIKE, 'foo, bar')], $this->column->getFilters('asource')); + } + + public function testIsNullFilter() + { + $this->column->setData(['operator' => Column::OPERATOR_ISNULL]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_ISNULL), + new Filter(Column::OPERATOR_EQ, ''), + ], $this->column->getFilters('asource')); + $this->assertEquals(Column::DATA_DISJUNCTION, $this->column->getDataJunction()); + } + + public function testIsNotNullFilter() + { + $this->column->setData(['operator' => Column::OPERATOR_ISNOTNULL]); + + $this->assertEquals([ + new Filter(Column::OPERATOR_ISNOTNULL), + new Filter(Column::OPERATOR_NEQ, ''), + ], $this->column->getFilters('asource')); + } + + public function testRenderCellWithoutCallback() + { + $values = ['foo, bar']; + + $result = $this->column->renderCell( + $values, + $this->createMock(Row::class), + $this->createMock(Router::class) + ); + + $this->assertEquals($result, $values); + } + + public function testRenderCellWithCallback() + { + $values = ['foo, bar']; + $this->column->manipulateRenderCell(fn($value, $row, $router) => ['foobar']); + + $result = $this->column->renderCell( + $values, + $this->createMock(Row::class), + $this->createMock(Router::class) + ); + + $this->assertEquals($result, ['foobar']); + } +} diff --git a/Tests/Grid/Column/TextColumnTest.php b/Tests/Grid/Column/TextColumnTest.php new file mode 100644 index 00000000..88cb935c --- /dev/null +++ b/Tests/Grid/Column/TextColumnTest.php @@ -0,0 +1,61 @@ +assertEquals('text', $this->column->getType()); + } + + public function testIsQueryValid() + { + $this->assertTrue($this->column->isQueryValid('foo')); + $this->assertTrue($this->column->isQueryValid(['foo', 1, 'bar', null])); + $this->assertFalse($this->column->isQueryValid(1)); + } + + public function testNullOperatorFilters() + { + $this->column->setData(['operator' => Column::OPERATOR_ISNULL]); + $this->assertEquals([ + new Filter(Column::OPERATOR_ISNULL), + new Filter(Column::OPERATOR_EQ, ''), + ], $this->column->getFilters('asource')); + $this->assertEquals(Column::DATA_DISJUNCTION, $this->column->getDataJunction()); + } + + public function testNotNullOperatorFilters() + { + $this->column->setData(['operator' => Column::OPERATOR_ISNOTNULL]); + $this->assertEquals([ + new Filter(Column::OPERATOR_ISNOTNULL), + new Filter(Column::OPERATOR_NEQ, ''), + ], $this->column->getFilters('asource')); + } + + public function testOtherOperatorFilters() + { + $operators = array_flip(Column::getAvailableOperators()); + unset($operators[Column::OPERATOR_ISNOTNULL]); + unset($operators[Column::OPERATOR_ISNULL]); + + foreach (array_keys($operators) as $operator) { + $this->column->setData(['operator' => $operator]); + $this->assertEmpty($this->column->getFilters('asource')); + } + } + + public function setUp(): void + { + $this->column = new TextColumn(); + } +} diff --git a/Tests/Grid/Column/TimeColumnTest.php b/Tests/Grid/Column/TimeColumnTest.php new file mode 100644 index 00000000..b35b312c --- /dev/null +++ b/Tests/Grid/Column/TimeColumnTest.php @@ -0,0 +1,16 @@ +assertEquals('time', $column->getType()); + } +} diff --git a/Tests/Grid/Column/UntypedColumnTest.php b/Tests/Grid/Column/UntypedColumnTest.php new file mode 100644 index 00000000..bd950fff --- /dev/null +++ b/Tests/Grid/Column/UntypedColumnTest.php @@ -0,0 +1,37 @@ +assertEquals($params, $column->getParams()); + } + + public function testSetType() + { + $type = 'text'; + + $column = new UntypedColumn(); + $column->setType($type); + + $this->assertEquals($type, $column->getType()); + } + + public function getType() + { + $type = 'text'; + + $column = new UntypedColumn(); + $column->setType($type); + + $this->assertEquals($type, $column->getType()); + } +} diff --git a/Tests/Grid/ColumnsTest.php b/Tests/Grid/ColumnsTest.php new file mode 100644 index 00000000..97863dd2 --- /dev/null +++ b/Tests/Grid/ColumnsTest.php @@ -0,0 +1,238 @@ +columns->getIterator(); + $this->assertInstanceOf(ColumnsIterator::class, $iterator); + } + + public function testAddColumn() + { + $column = $this->buildColumnMocks(1); + $this->columns->addColumn($column); + + $this->equalTo(1); + } + + public function testAddColumnsOrder() + { + [$column1, $column2, $column3, $column4] = $this->buildColumnMocks(4); + + $this->columns + ->addColumn($column1) + ->addColumn($column2, 1) + ->addColumn($column3, 2) + ->addColumn($column4, -1) + ; + + $this->assertSame([$column2, $column3, $column4, $column1], $this->columns->getColumns()); + } + + public function testRaiseExceptionIfGetColumnByIdDoesNotExists() + { + $this->expectException(\InvalidArgumentException::class); + + $column = $this->buildColumnMocks(1); + $this->columns->addColumn($column); + + $this->columns->getColumnById('foo'); + } + + public function testGetColumnById() + { + $column = $this->buildColumnMocks(1); + $column->method('getId')->willReturn('foo'); + $this->columns->addColumn($column); + + $this->assertSame($column, $this->columns->getColumnById('foo')); + } + + public function testHasColumnById() + { + $column = $this->buildColumnMocks(1); + $column->method('getId')->willReturn('foo'); + $this->columns->addColumn($column); + + $this->assertSame($column, $this->columns->hasColumnById('foo', true)); + $this->assertTrue($this->columns->hasColumnById('foo', false)); + } + + public function testRaiseExceptionIfGetPrimaryColumnDoesNotExists() + { + $this->expectException(\InvalidArgumentException::class); + + $column = $this->buildColumnMocks(1); + $column->method('isPrimary')->willReturn(false); + $this->columns->addColumn($column); + + $this->columns->getPrimaryColumn(); + } + + public function testGetPrimaryColumn() + { + [$column1, $column2, $column3] = $this->buildColumnMocks(3); + + $column1->method('isPrimary')->willReturn(false); + $this->columns->addColumn($column1); + + $column2->method('isPrimary')->willReturn(true); + $this->columns->addColumn($column2); + + $column3->method('isPrimary')->willReturn(true); + $this->columns->addColumn($column3); + + $this->assertSame($column2, $this->columns->getPrimaryColumn()); + } + + public function testAddExtension() + { + $column1 = $this->createMock(Column::class); + $column1->method('getType')->willReturn('foo'); + + $column2 = $this->createMock(Column::class); + $column2->method('getType')->willReturn('bar'); + + $this->columns + ->addExtension($column1) + ->addExtension($column2); + + $this->assertEquals(['foo' => $column1, 'bar' => $column2], $this->columns->getExtensions()); + } + + public function testHasExtensionForColumnType() + { + $column1 = $this->createMock(Column::class); + $column1->method('getType')->willReturn('foo'); + + $this->columns->addExtension($column1); + + $this->assertTrue($this->columns->hasExtensionForColumnType('foo')); + $this->assertFalse($this->columns->hasExtensionForColumnType('bar')); + } + + public function testGetExtensionForColumnType() + { + $column1 = $this->createMock(Column::class); + $column1->method('getType')->willReturn('foo'); + + $this->columns->addExtension($column1); + + $this->assertEquals($column1, $this->columns->getExtensionForColumnType('foo')); + } + + public function testGetHash() + { + $this->assertEquals('', $this->columns->getHash()); + + [$column1, $column2, $column3, $column4] = $this->buildColumnMocks(4); + + $column1->method('getId')->willReturn('this'); + $column2->method('getId')->willReturn('Is'); + $column3->method('getId')->willReturn('The'); + $column4->method('getId')->willReturn('Hash'); + + $this->columns + ->addColumn($column1) + ->addColumn($column2) + ->addColumn($column3) + ->addColumn($column4); + + $this->assertEquals('thisIsTheHash', $this->columns->getHash()); + } + + public function testSetColumnsOrder() + { + [$column1, $column2, $column3] = $this->buildColumnMocks(3); + + $column1->method('getId')->willReturn('col1'); + $column2->method('getId')->willReturn('col2'); + $column3->method('getId')->willReturn('col3'); + + $this->columns + ->addColumn($column1) + ->addColumn($column2) + ->addColumn($column3); + $this->columns->setColumnsOrder(['col3', 'col1', 'col2']); + + $this->assertSame([$column3, $column1, $column2], $this->columns->getColumns()); + } + + public function testPartialSetColumnsOrderAndKeepOthers() + { + [$column1, $column2, $column3] = $this->buildColumnMocks(3); + + $column1->method('getId')->willReturn('col1'); + $column2->method('getId')->willReturn('col2'); + $column3->method('getId')->willReturn('col3'); + + $this->columns + ->addColumn($column1) + ->addColumn($column2) + ->addColumn($column3); + $this->columns->setColumnsOrder(['col3', 'col2'], true); + + $this->assertSame([$column3, $column2, $column1], $this->columns->getColumns()); + } + + public function testPartialSetColumnsOrderWithoutKeepOthers() + { + [$column1, $column2, $column3] = $this->buildColumnMocks(3); + + $column1->method('getId')->willReturn('col1'); + $column2->method('getId')->willReturn('col2'); + $column3->method('getId')->willReturn('col3'); + + $this->columns + ->addColumn($column1) + ->addColumn($column2) + ->addColumn($column3); + $this->columns->setColumnsOrder(['col3', 'col2'], false); + + $this->assertSame([$column3, $column2], $this->columns->getColumns()); + } + + /** + * @param int $number + * + * @return array|\PHPUnit_Framework_MockObject_MockObject[]|\PHPUnit_Framework_MockObject_MockObject + */ + private function buildColumnMocks($number) //: array|\PHPUnit_Framework_MockObject_MockObject|\APY\DataGridBundle\Grid\Column\Column + { + $mocks = []; + for ($i = 0; $i < $number; ++$i) { + $column = $this->createMock(Column::class); + $column + ->expects($this->once()) + ->method('setAuthorizationChecker') + ->with($this->authChecker); + + $mocks[] = $column; + } + + if ($number == 1) { + return current($mocks); + } + + return $mocks; + } + + public function setUp(): void + { + $this->authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $this->columns = new Columns($this->authChecker); + } +} diff --git a/Tests/Grid/FilterTest.php b/Tests/Grid/FilterTest.php new file mode 100644 index 00000000..8b56277f --- /dev/null +++ b/Tests/Grid/FilterTest.php @@ -0,0 +1,72 @@ +assertEquals('like', $filter1->getOperator()); + $this->assertEquals('foo', $filter1->getValue()); + $this->assertEquals('column1', $filter1->getColumnName()); + } + + public function testSetOperator() + { + $filter = new Filter('like'); + $filter->setOperator('nlike'); + + $this->assertEquals('nlike', $filter->getOperator()); + } + + public function testGetOperator() + { + $filter = new Filter('like'); + + $this->assertEquals('like', $filter->getOperator()); + } + + public function testSetValue() + { + $filter = new Filter('like'); + $filter->setValue('foo'); + + $this->assertEquals('foo', $filter->getValue()); + } + + public function testGetValue() + { + $filter = new Filter('like', 'foo'); + + $this->assertEquals('foo', $filter->getValue()); + } + + public function testSetColumnName() + { + $filter = new Filter('like'); + $filter->setColumnName('col1'); + + $this->assertEquals('col1', $filter->getColumnName()); + } + + public function testGetColumnName() + { + $filter = new Filter('like', null, 'col1'); + + $this->assertEquals('col1', $filter->getColumnName()); + } + + public function testHasColumnName() + { + $filter1 = new Filter('like', 'foo', 'col1'); + $filter2 = new Filter('like'); + + $this->assertTrue($filter1->hasColumnName()); + $this->assertFalse($filter2->hasColumnName()); + } +} diff --git a/Tests/Grid/GridBuilderTest.php b/Tests/Grid/GridBuilderTest.php new file mode 100755 index 00000000..f192a23d --- /dev/null +++ b/Tests/Grid/GridBuilderTest.php @@ -0,0 +1,197 @@ +getContainer(); + //$container = self::$container; + //$this->container = $container; + $self = $this; + $authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $this->authChecker = $authChecker; + $this->container = $this->createMock(Container::class); + $this->container->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function ($param) use ($self) { + switch ($param) { + case 'router': + return $self->createMock(RouterInterface::class); + break; + case 'request_stack': + $request = new Request([], [], ['key' => 'value']); + $session = new Session(); + $request->setSession($session); + $requestStack = new RequestStack(); + $requestStack->push($request); + return $requestStack; + break; + case 'security.authorization_checker': + return $authChecker; + break; + } + })); + $this->twig = $this->createMock(Environment::class); + $this->factory = $this->createMock(GridFactoryInterface::class); + $this->builder = new GridBuilder($this->container, $this->authChecker, $this->twig, $this->factory, 'name'); + } + + public function testAddUnexpectedType() + { + $this->expectException(UnexpectedTypeException::class); + + $this->builder->add('foo', 123); + $this->builder->add('foo', ['test']); + } + + public function testAddColumnTypeString() + { + $this->assertFalse($this->builder->has('foo')); + + $this->factory->expects($this->once()) + ->method('createColumn') + ->with('foo', 'text', []) + ->willReturn($this->createMock(Column::class)); + + $this->builder->add('foo', 'text'); + + $this->assertTrue($this->builder->has('foo')); + } + + public function testAddColumnType() + { + $this->factory->expects($this->never())->method('createColumn'); + + $this->assertFalse($this->builder->has('foo')); + $this->builder->add('foo', $this->createMock(Column::class)); + $this->assertTrue($this->builder->has('foo')); + } + + public function testAddIsFluent() + { + $builder = $this->builder->add('name', 'text', ['key' => 'value']); + $this->assertSame($builder, $this->builder); + } + + public function testGetUnknown() + { + $this->expectException( + InvalidArgumentException::class + ); + + $this->builder->get('foo'); + } + + public function testGetExplicitColumnType() + { + $expectedColumn = $this->createMock(Column::class); + + $this->factory->expects($this->once()) + ->method('createColumn') + ->with('foo', 'text', []) + ->willReturn($expectedColumn); + + $this->builder->add('foo', 'text'); + + $column = $this->builder->get('foo'); + + $this->assertSame($expectedColumn, $column); + } + + public function testHasColumnType() + { + $this->factory->expects($this->once()) + ->method('createColumn') + ->with('foo', 'text', []) + ->willReturn($this->createMock(Column::class)); + + $this->builder->add('foo', 'text'); + + $this->assertTrue($this->builder->has('foo')); + } + + public function assertHasNotColumnType() + { + $this->assertFalse($this->builder->has('foo')); + } + + public function testRemove() + { + $this->factory->expects($this->once()) + ->method('createColumn') + ->with('foo', 'text', []) + ->willReturn($this->createMock(Column::class)); + + $this->builder->add('foo', 'text'); + + $this->assertTrue($this->builder->has('foo')); + $this->builder->remove('foo'); + $this->assertFalse($this->builder->has('foo')); + } + + public function testRemoveIsFluent() + { + $builder = $this->builder->remove('foo'); + $this->assertSame($builder, $this->builder); + } + + public function testGetGrid() + { + $this->assertInstanceOf(Grid::class, $this->builder->getGrid()); + } + + protected function tearDown(): void + { + $this->factory = null; + //$this->builder = null; + } +} diff --git a/Tests/Grid/GridConfigBuilderTest.php b/Tests/Grid/GridConfigBuilderTest.php new file mode 100644 index 00000000..871de235 --- /dev/null +++ b/Tests/Grid/GridConfigBuilderTest.php @@ -0,0 +1,282 @@ + 'foo', 'bar' => 'bar']; + + private \APY\DataGridBundle\Grid\GridConfigBuilder $gridConfigBuilder; + + public function testGetName() + { + $this->assertEquals($this->name, $this->gridConfigBuilder->getName()); + } + + public function testSetSource() + { + $source = $this->createMock(Source::class); + $this->gridConfigBuilder->setSource($source); + + $this->assertSame($source, $this->gridConfigBuilder->getSource()); + } + + public function testGetSource() + { + $source = $this->createMock(Source::class); + $this->gridConfigBuilder->setSource($source); + + $this->assertSame($source, $this->gridConfigBuilder->getSource()); + } + + public function testSetType() + { + $type = $this->createMock(GridTypeInterface::class); + $this->gridConfigBuilder->setType($type); + + $this->assertSame($type, $this->gridConfigBuilder->getType()); + } + + public function testGetType() + { + $type = $this->createMock(GridTypeInterface::class); + $this->gridConfigBuilder->setType($type); + + $this->assertSame($type, $this->gridConfigBuilder->getType()); + } + + public function testSetRoute() + { + $route = 'vendor.bundle.foo_route'; + $this->gridConfigBuilder->setRoute($route); + + $this->assertEquals($route, $this->gridConfigBuilder->getRoute()); + } + + public function testGetRoute() + { + $route = 'vendor.bundle.foo_route'; + $this->gridConfigBuilder->setRoute($route); + + $this->assertEquals($route, $this->gridConfigBuilder->getRoute()); + } + + public function testSetRouteParameters() + { + $routeParams = ['foo' => 'foo', 'bar' => 'bar']; + $this->gridConfigBuilder->setRouteParameters($routeParams); + + $this->assertEquals($routeParams, $this->gridConfigBuilder->getRouteParameters()); + } + + public function testGetRouteParameters() + { + $routeParams = ['foo' => 'foo', 'bar' => 'bar']; + $this->gridConfigBuilder->setRouteParameters($routeParams); + + $this->assertEquals($routeParams, $this->gridConfigBuilder->getRouteParameters()); + } + + public function testSetPersistence() + { + $persistence = true; + $this->gridConfigBuilder->setPersistence($persistence); + + $this->assertEquals($persistence, $this->gridConfigBuilder->getPersistence()); + } + + public function testIsPersited() + { + $persisted = false; + $this->gridConfigBuilder->setPersistence($persisted); + + $this->assertFalse($this->gridConfigBuilder->isPersisted()); + } + + public function testSetPage() + { + $page = 1; + $this->gridConfigBuilder->setPage($page); + + $this->assertEquals($page, $this->gridConfigBuilder->getPage()); + } + + public function testGetPage() + { + $page = 5; + $this->gridConfigBuilder->setPage($page); + + $this->assertEquals($page, $this->gridConfigBuilder->getPage()); + } + + public function testGetOptions() + { + $this->assertEquals($this->options, $this->gridConfigBuilder->getOptions()); + } + + public function testHasOption() + { + $this->assertTrue($this->gridConfigBuilder->hasOption('foo')); + $this->assertFalse($this->gridConfigBuilder->hasOption('foobar')); + } + + public function testGetOption() + { + $this->assertEquals('foo', $this->gridConfigBuilder->getOption('foo')); + $this->assertEquals('default', $this->gridConfigBuilder->getOption('foobar', 'default')); + $this->assertNull($this->gridConfigBuilder->getOption('foobar')); + } + + public function testSetMaxPerPage() + { + $limit = 50; + $this->gridConfigBuilder->setMaxPerPage($limit); + + $this->assertEquals($limit, $this->gridConfigBuilder->getMaxPerPage()); + } + + public function testGetMaxPerPage() + { + $limit = 100; + $this->gridConfigBuilder->setMaxPerPage($limit); + + $this->assertEquals($limit, $this->gridConfigBuilder->getMaxPerPage()); + } + + public function testSetMaxResults() + { + $maxResults = 50; + $this->gridConfigBuilder->setMaxResults($maxResults); + + $this->assertEquals($maxResults, $this->gridConfigBuilder->getMaxResults()); + } + + public function testGetMaxResults() + { + $maxResults = 100; + $this->gridConfigBuilder->setMaxResults($maxResults); + + $this->assertEquals($maxResults, $this->gridConfigBuilder->getMaxResults()); + } + + public function testSetSortable() + { + $sortable = true; + $this->gridConfigBuilder->setSortable($sortable); + + $this->assertEquals(true, $this->gridConfigBuilder->isSortable()); + } + + public function testIsSortable() + { + $sortable = false; + $this->gridConfigBuilder->setSortable($sortable); + + $this->assertFalse($this->gridConfigBuilder->isSortable()); + } + + public function testSetFilterable() + { + $filterable = false; + $this->gridConfigBuilder->setFilterable($filterable); + + $this->assertEquals($filterable, $this->gridConfigBuilder->isFilterable()); + } + + public function testIsFilterable() + { + $filterable = true; + $this->gridConfigBuilder->setFilterable($filterable); + + $this->assertTrue($this->gridConfigBuilder->isFilterable()); + } + + public function testSetOrder() + { + $order = 'asc'; + $this->gridConfigBuilder->setOrder($order); + + $this->assertEquals($order, $this->gridConfigBuilder->getOrder()); + } + + public function testGetOrder() + { + $order = 'desc'; + $this->gridConfigBuilder->setOrder($order); + + $this->assertEquals($order, $this->gridConfigBuilder->getOrder()); + } + + public function testSetSortBy() + { + $sortBy = 'foo'; + $this->gridConfigBuilder->setSortBy($sortBy); + + $this->assertEquals($sortBy, $this->gridConfigBuilder->getSortBy()); + } + + public function testGetSortBy() + { + $sortBy = 'bar'; + $this->gridConfigBuilder->setSortBy($sortBy); + + $this->assertEquals($sortBy, $this->gridConfigBuilder->getSortBy()); + } + + public function testSetGroupBy() + { + $groupBy = 'foo'; + $this->gridConfigBuilder->setGroupBy($groupBy); + + $this->assertEquals($groupBy, $this->gridConfigBuilder->getGroupBy()); + } + + public function testGetGroupBy() + { + $groupBy = ['foo', 'bar']; + $this->gridConfigBuilder->setGroupBy($groupBy); + + $this->assertEquals($groupBy, $this->gridConfigBuilder->getGroupBy()); + } + + public function testAddAction() + { + $action1 = $this->createMock(RowActionInterface::class); + $action1->method('getColumn')->willReturn('foo'); + + $action2 = $this->createMock(RowActionInterface::class); + $action2->method('getColumn')->willReturn('bar'); + + $action3 = $this->createMock(RowActionInterface::class); + $action3->method('getColumn')->willReturn('bar'); + + $this->gridConfigBuilder + ->addAction($action1) + ->addAction($action2) + ->addAction($action3); + + $this->assertEquals(['foo' => [$action1], 'bar' => [$action2, $action3]], $this->gridConfigBuilder->getActions()); + } + + public function testGetGridConfig() + { + $this->assertInstanceOf(GridConfigBuilder::class, $this->gridConfigBuilder->getGridConfig()); + } + + /** + * {@inheritdoc} + */ + protected function setUp(): void + { + $this->gridConfigBuilder = new GridConfigBuilder($this->name, $this->options); + } +} diff --git a/Tests/Grid/GridFactoryTest.php b/Tests/Grid/GridFactoryTest.php new file mode 100755 index 00000000..e924d782 --- /dev/null +++ b/Tests/Grid/GridFactoryTest.php @@ -0,0 +1,195 @@ +expectException(UnexpectedTypeException::class); + $this->factory->create(1234); + $this->factory->create(['foo']); + $this->factory->create(new \stdClass()); + } + + public function testCreateWithTypeString() + { + $this->registry->expects($this->once()) + ->method('getType') + ->with('foo') + ->willReturn($this->createMock(GridTypeInterface::class)); + + $this->assertInstanceOf(Grid::class, $this->factory->create('foo')); + } + + public function testCreateWithTypeObject() + { + $this->registry->expects($this->never())->method('getType'); + + $this->assertInstanceOf(Grid::class, $this->factory->create(new GridType())); + } + + public function testCreateBuilderWithDefaultType() + { + $defaultType = new GridType(); + + $this->registry->expects($this->once()) + ->method('getType') + ->with('grid') + ->willReturn($defaultType); + + $builder = $this->factory->createBuilder(); + + $this->assertSame($defaultType, $builder->getType()); + } + + public function testCreateBuilder() + { + $givenOptions = ['a' => 1, 'b' => 2]; + $resolvedOptions = ['a' => 1, 'b' => 2, 'c' => 3]; + + $type = $this->createMock(GridTypeInterface::class); + + $type->expects($this->once()) + ->method('getName') + ->willReturn('TYPE'); + + $type->expects($this->once()) + ->method('configureOptions') + ->with($this->callback(function ($resolver) use ($resolvedOptions) { + if (!$resolver instanceof OptionsResolver) { + return false; + } + + $resolver->setDefaults($resolvedOptions); + + return true; + })); + + $type->expects($this->once()) + ->method('buildGrid') + ->with($this->callback(fn ($builder) => $builder instanceof GridBuilder && $builder->getName() == 'TYPE'), $resolvedOptions); + + $builder = $this->factory->createBuilder($type, null, $givenOptions); + + $this->assertInstanceOf(GridBuilderInterface::class, $builder); + $this->assertSame($type, $builder->getType()); + $this->assertSame('TYPE', $builder->getName()); + $this->assertEquals($resolvedOptions, $builder->getOptions()); + $this->assertNull($builder->getSource()); + } + + public function testCreateColumnWithUnexpectedType() + { + $this->expectException(UnexpectedTypeException::class); + $this->factory->createColumn('foo', 1234); + } + + public function testCreateColumnWithTypeString() + { + $expectedColumn = new TextColumn(); + + $this->registry->expects($this->once()) + ->method('getColumn') + ->with('text') + ->willReturn($expectedColumn); + + $column = $this->factory->createColumn('foo', 'text', ['title' => 'bar']); + + $this->assertInstanceOf(TextColumn::class, $column); + $this->assertEquals('text', $column->getType()); + $this->assertEquals('foo', $column->getId()); + $this->assertEquals('bar', $column->getTitle()); + $this->assertEquals('foo', $column->getField()); + $this->assertTrue($column->isVisibleForSource()); + } + + public function testCreateColumnWithObject() + { + $column = $this->factory->createColumn('foo', new TextColumn(), ['title' => 'bar']); + + $this->assertInstanceOf(TextColumn::class, $column); + $this->assertEquals('text', $column->getType()); + $this->assertEquals('foo', $column->getId()); + $this->assertEmpty($column->getTitle()); + $this->assertNull($column->getField()); + $this->assertFalse($column->isVisibleForSource()); + } + + protected function setUp(): void + { + $self = $this; + + $this->container = $this->createMock(Container::class); + $this->authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $this->twig = $this->createMock(Environment::class); + $this->container->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function ($param) use ($self) { + switch ($param) { + case 'router': + return $self->createMock(RouterInterface::class); + break; + case 'request_stack': + $request = new Request([], [], ['key' => 'value']); + $session = new Session(); + $request->setSession($session); + $requestStack = new RequestStack(); + $requestStack->push($request); + + return $requestStack; + break; + case 'security.authorization_checker': + return $this->authChecker; + break; + } + })); + + $this->registry = $this->createMock(GridRegistryInterface::class); + $this->builder = $this->createMock(GridBuilderInterface::class); + $this->factory = new GridFactory($this->container, $this->authChecker, $this->twig, $this->registry); + } +} diff --git a/Tests/Grid/GridManagerTest.php b/Tests/Grid/GridManagerTest.php new file mode 100644 index 00000000..f2f991c8 --- /dev/null +++ b/Tests/Grid/GridManagerTest.php @@ -0,0 +1,604 @@ +assertInstanceOf(\SplObjectStorage::class, $this->gridManager->getIterator()); + } + + public function testCreateGridWithoutId() + { + $grid = $this->createMock(Grid::class); + $this + ->container + ->method('get') + ->with('grid') + ->willReturn($grid); + + $grids = new \SplObjectStorage(); + $grids->attach($grid); + + $grid + ->expects($this->never()) + ->method('setId'); + + $this->assertEquals($grid, $this->gridManager->createGrid()); + + $this->assertEquals($grids, $this->gridManager->getGrids()); + } + + public function testCreateGridWithId() + { + $grid = $this->createMock(Grid::class); + $this + ->container + ->method('get') + ->with('grid') + ->willReturn($grid); + + $grids = new \SplObjectStorage(); + $grids->attach($grid); + + $gridId = 'gridId'; + $grid + ->expects($this->atLeastOnce()) + ->method('setId') + ->with($gridId); + + $this->assertEquals($grid, $this->gridManager->createGrid($gridId)); + + $this->assertEquals($grids, $this->gridManager->getGrids()); + } + + public function testReturnsManagedGridCount() + { + $grid = $this->createMock(Grid::class); + $this + ->container + ->method('get') + ->with('grid') + ->willReturn($grid); + + $this->gridManager->createGrid(); + + $this->assertEquals(1, $this->gridManager->count()); + } + + public function testSetRouteUrl() + { + $routeUrl = 'aRouteUrl'; + $this->gridManager->setRouteUrl($routeUrl); + + $this->assertEquals($routeUrl, $this->gridManager->getRouteUrl()); + } + + public function testGetRouteUrl() + { + $routeUrl = 'aRouteUrl'; + $this->gridManager->setRouteUrl($routeUrl); + + $this->assertEquals($routeUrl, $this->gridManager->getRouteUrl()); + } + + public function testItThrowsExceptionWhenCheckForRedirectAndGridsNotSetted() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage(GridManager::NO_GRID_EX_MSG); + + $this->gridManager->isReadyForRedirect(); + } + + public function testItThrowsExceptionWhenTwoDifferentGridsReturnsSameHashDuringCheckForRedirect() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage(GridManager::SAME_GRID_HASH_EX_MSG); + + $sameHash = 'hashValue'; + + $this->stubTwoGridsForRedirect($sameHash, null, null, $sameHash, null, null); + + $this->gridManager->isReadyForRedirect(); + } + + public function testNoGridsReadyForRedirect() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + $this->stubTwoGridsForRedirect($grid1Hash, null, false, $grid2Hash, null, false); + + $this->assertFalse($this->gridManager->isReadyForRedirect()); + } + + public function testAtLeastOneGridReadyForRedirect() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + $this->stubTwoGridsForRedirect($grid1Hash, null, false, $grid2Hash, null, true); + + $this->assertTrue($this->gridManager->isReadyForRedirect()); + } + + public function testItRewindGridListWhenCheckingTwoTimesIfReadyForRedirect() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + $grid = $this->createMock(Grid::class); + $grid + ->method('getHash') + ->willReturn($grid1Hash); + + $grid2 = $this->createMock(Grid::class); + $grid2 + ->method('getHash') + ->willReturn($grid2Hash); + + $this + ->container + ->method('get') + ->with('grid') + ->willReturnOnConsecutiveCalls($grid, $grid2); + + $grid + ->expects($this->exactly(2)) + ->method('isReadyForRedirect'); + $grid2 + ->expects($this->exactly(2)) + ->method('isReadyForRedirect'); + + $this->gridManager->createGrid(); + $this->gridManager->createGrid(); + + $this->gridManager->isReadyForRedirect(); + $this->gridManager->isReadyForRedirect(); + } + + public function testItTakesFirstGridUrlAsGlobalRouteUrl() + { + $grid1Hash = 'hashValue1'; + $route1Url = 'route1Url'; + + $grid2Hash = 'hashValue2'; + $route2Url = 'route2Url'; + + $this->stubTwoGridsForRedirect($grid1Hash, $route1Url, null, $grid2Hash, $route2Url, null); + + $this->gridManager->isReadyForRedirect(); + + $this->assertEquals($route1Url, $this->gridManager->getRouteUrl()); + } + + public function testItIgnoresEveryGridUrlIfRouteUrlAlreadySetted() + { + $grid1Hash = 'hashValue1'; + $route1Url = 'route1Url'; + + $grid2Hash = 'hashValue2'; + $route2Url = 'route2Url'; + + $this->stubTwoGridsForRedirect($grid1Hash, $route1Url, null, $grid2Hash, $route2Url, null); + + $settedRouteUrl = 'settedRouteUrl'; + $this->gridManager->setRouteUrl($settedRouteUrl); + + $this->gridManager->isReadyForRedirect(); + + $this->assertEquals($settedRouteUrl, $this->gridManager->getRouteUrl()); + } + + public function testItThrowsExceptionWhenCheckForExportAndGridsNotSetted() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage(GridManager::NO_GRID_EX_MSG); + + $this->gridManager->isReadyForExport(); + } + + public function testItThrowsExceptionWhenTwoDifferentGridsReturnsSameHashDuringCheckForExport() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage(GridManager::SAME_GRID_HASH_EX_MSG); + + $sameHash = 'hashValue'; + + $this->stubTwoGridsForExport($sameHash, null, $sameHash, null); + + $this->gridManager->isReadyForExport(); + } + + public function testNoGridsReadyForExport() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + $this->stubTwoGridsForExport($grid1Hash, false, $grid2Hash, false); + + $this->assertFalse($this->gridManager->isReadyForExport()); + } + + public function testAtLeastOneGridReadyForExport() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + [$grid, $grid2] = $this->stubTwoGridsForExport($grid1Hash, false, $grid2Hash, true); + + $this->assertTrue($this->gridManager->isReadyForExport()); + + $this->assertEquals($grid2, $this->gridManager->getExportGrid()); + } + + public function testItRewindGridListWhenCheckingTwoTimesIfReadyForExport() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + $grid = $this->createMock(Grid::class); + $grid + ->method('getHash') + ->willReturn($grid1Hash); + + $grid2 = $this->createMock(Grid::class); + $grid2 + ->method('getHash') + ->willReturn($grid2Hash); + + $this + ->container + ->method('get') + ->with('grid') + ->willReturnOnConsecutiveCalls($grid, $grid2); + + $grid + ->expects($this->exactly(2)) + ->method('isReadyForExport'); + $grid2 + ->expects($this->exactly(2)) + ->method('isReadyForExport'); + + $this->gridManager->createGrid(); + $this->gridManager->createGrid(); + + $this->gridManager->isReadyForExport(); + $this->gridManager->isReadyForExport(); + } + + public function testNoGridsHasMassActionRedirect() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + $this->stubTwoGridForMassAction($grid1Hash, false, $grid2Hash, false); + + $this->assertFalse($this->gridManager->isMassActionRedirect()); + } + + public function testAtLeastOneGridHasMassActionRedirect() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + [$grid, $grid2] = $this->stubTwoGridForMassAction($grid1Hash, false, $grid2Hash, true); + + $this->assertTrue($this->gridManager->isMassActionRedirect()); + + $this->assertEquals($grid2, $this->gridManager->getMassActionGrid()); + } + + public function testItRewindGridListWhenCheckingTwoTimesIfHasMassActionRedirect() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + $grid = $this->createMock(Grid::class); + $grid + ->method('getHash') + ->willReturn($grid1Hash); + + $grid2 = $this->createMock(Grid::class); + $grid2 + ->method('getHash') + ->willReturn($grid2Hash); + + $this + ->container + ->method('get') + ->with('grid') + ->willReturnOnConsecutiveCalls($grid, $grid2); + + $grid + ->expects($this->exactly(2)) + ->method('isMassActionRedirect'); + $grid2 + ->expects($this->exactly(2)) + ->method('isMassActionRedirect'); + + $this->gridManager->createGrid(); + $this->gridManager->createGrid(); + + $this->gridManager->isMassActionRedirect(); + $this->gridManager->isMassActionRedirect(); + } + + public function testGridResponseRedirect() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + $this->stubTwoGridsForRedirect($grid1Hash, null, false, $grid2Hash, null, true); + + $routeUrl = 'aRouteUrl'; + $this->gridManager->setRouteUrl($routeUrl); + + $this->assertEquals($routeUrl, $this->gridManager->getGridManagerResponse()->getTargetUrl()); + } + + public function testGridResponseExport() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + [$grid, $grid2] = $this->stubTwoGridsForExport($grid1Hash, false, $grid2Hash, true); + + $response = new Response(); + $grid2 + ->method('getExportResponse') + ->willReturn($response); + + $this->assertEquals($response, $this->gridManager->getGridManagerResponse()); + } + + public function testGridResponseMassActionRedirect() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + [$grid, $grid2] = $this->stubTwoGridForMassAction($grid1Hash, false, $grid2Hash, true); + + $response = new Response(); + $grid2 + ->method('getMassActionResponse') + ->willReturn($response); + + $this->assertEquals($response, $this->gridManager->getGridManagerResponse()); + } + + public function testGetGridResponseWithoutParams() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + [$grid, $grid2] = $this->stubTwoGrids($grid1Hash, $grid2Hash); + + $this->assertEquals(['grid1' => $grid, 'grid2' => $grid2], $this->gridManager->getGridManagerResponse()); + } + + public function testGetGridResponseWithoutView() + { + $grid1Hash = 'hashValue1'; + $grid2Hash = 'hashValue2'; + + [$grid, $grid2] = $this->stubTwoGrids($grid1Hash, $grid2Hash); + + $param1 = 'foo'; + $param2 = 'bar'; + $params = [$param1, $param2]; + $this->assertEquals(['grid1' => $grid, 'grid2' => $grid2, $param1, $param2], $this->gridManager->getGridManagerResponse($params)); + } + + /* + public function testGetGridWithViewWithoutParams() + { + $grid1Hash = 'hashValue1'; + + $grid = $this->createMock(Grid::class); + $grid + ->method('getHash') + ->willReturn($grid1Hash); + + $twig = $this->createMock(Environment::class); + + $containerGetMap = [ + ['grid', Container::EXCEPTION_ON_INVALID_REFERENCE, $grid], + ['twig', Container::EXCEPTION_ON_INVALID_REFERENCE, $twig], + ]; + + $this + ->container + ->method('get') + ->will($this->returnValueMap($containerGetMap)); + + $this->gridManager->createGrid(); + + $view = 'aView'; + + $content = "test123"; + + $twig + ->method('render') + ->with($view, ['grid1' => $grid]) + ->willReturn($content); + + // Can't mock the Response object, just check for the content Twig does return + $this->assertEquals($content, $this->gridManager->getGridManagerResponse($view)->getContent()); + } + */ + + /* + public function testGetGridWithViewWithViewAndParams() + { + $grid1Hash = 'hashValue1'; + + $grid = $this->createMock(Grid::class); + $grid + ->method('getHash') + ->willReturn($grid1Hash); + + $twig = $this->createMock(Environment::class); + + $containerGetMap = [ + ['grid', Container::EXCEPTION_ON_INVALID_REFERENCE, $grid], + ['twig', Container::EXCEPTION_ON_INVALID_REFERENCE, $twig], + ]; + + $this + ->container + ->method('get') + ->will($this->returnValueMap($containerGetMap)); + + $this->gridManager->createGrid(); + + $view = 'aView'; + + $param1 = 'foo'; + $param2 = 'bar'; + $params = [$param1, $param2]; + + $response = $this->createMock(Response::class); + + $twig + ->method('render') + ->with($view, ['grid1' => $grid, $param1, $param2]) + ->willReturn($response); + + // TODO grid Manager response makes a new Response object internal, so i can't mock it + $this->assertEquals($response, $this->gridManager->getGridManagerResponse($view, $params, $response)); + } + */ + public function setUp(): void + { + $this->container = $this->createMock(Container::class); + $this->twig = $this->createMock(Environment::class); + $this->gridManager = new GridManager($this->container, $this->twig); + } + + /** + * @param string $grid1Hash + * @param string $route1Url + * @param bool $grid1ReadyForRedirect + * @param string $grid2Hash + * @param string $route2Url + * @param bool $grid2ReadyForRedirect + */ + private function stubTwoGridsForRedirect( + $grid1Hash, + $route1Url, + $grid1ReadyForRedirect, + $grid2Hash, + $route2Url, + $grid2ReadyForRedirect + ) { + [$grid, $grid2] = $this->stubTwoGrids($grid1Hash, $grid2Hash); + + $grid + ->method('isReadyForRedirect') + ->willReturn($grid1ReadyForRedirect); + $grid + ->method('getRouteUrl') + ->willReturn($route1Url); + + $grid2 + ->method('isReadyForRedirect') + ->willReturn($grid2ReadyForRedirect); + $grid2 + ->method('getRouteUrl') + ->willReturn($route2Url); + } + + /** + * @param string $grid1Hash + * @param bool $grid1ReadyForExport + * @param string $grid2Hash + * @param bool $grid2ReadyForExport + * + * @return array + */ + private function stubTwoGridsForExport($grid1Hash, $grid1ReadyForExport, $grid2Hash, $grid2ReadyForExport) + { + [$grid, $grid2] = $this->stubTwoGrids($grid1Hash, $grid2Hash); + + $grid + ->method('isReadyForExport') + ->willReturn($grid1ReadyForExport); + + $grid2 + ->method('isReadyForExport') + ->willReturn($grid2ReadyForExport); + + return [$grid, $grid2]; + } + + /** + * @param string $grid1Hash + * @param bool $grid1IsMassActionRedirect + * @param string $grid2Hash + * @param bool $grid2IsMassActionRedirect + * + * @return array + */ + private function stubTwoGridForMassAction($grid1Hash, $grid1IsMassActionRedirect, $grid2Hash, $grid2IsMassActionRedirect) + { + [$grid, $grid2] = $this->stubTwoGrids($grid1Hash, $grid2Hash); + + $grid + ->method('isMassActionRedirect') + ->willReturn($grid1IsMassActionRedirect); + + $grid2 + ->method('isMassActionRedirect') + ->willReturn($grid2IsMassActionRedirect); + + return [$grid, $grid2]; + } + + /** + * @param string $grid1Hash + * @param string $grid2Hash + * + * @return array + */ + private function stubTwoGrids($grid1Hash, $grid2Hash) + { + $grid = $this->createMock(Grid::class); + $grid + ->method('getHash') + ->willReturn($grid1Hash); + + $grid2 = $this->createMock(Grid::class); + $grid2 + ->method('getHash') + ->willReturn($grid2Hash); + + $this + ->container + ->method('get') + ->with('grid') + ->willReturnOnConsecutiveCalls($grid, $grid2); + + $this->gridManager->createGrid(); + $this->gridManager->createGrid(); + + return [$grid, $grid2]; + } +} diff --git a/Tests/Grid/GridRegistryTest.php b/Tests/Grid/GridRegistryTest.php new file mode 100755 index 00000000..de4080e6 --- /dev/null +++ b/Tests/Grid/GridRegistryTest.php @@ -0,0 +1,118 @@ +expectException(TypeAlreadyExistsException::class); + + $type = $this->createTypeMock(); + $this->registry->addType($type); + $this->registry->addType($type); + } + + public function testAddType() + { + $this->assertFalse($this->registry->hasType('foo')); + $this->registry->addType($this->createTypeMock()); + $this->assertTrue($this->registry->hasType('foo')); + } + + public function testAddIsFluent() + { + $registry = $this->registry->addType($this->createTypeMock()); + $this->assertSame($registry, $this->registry); + } + + public function testGetTypeUnknown() + { + $this->expectException(TypeNotFoundException::class); + $this->registry->getType('foo'); + } + + public function testGetType() + { + $expectedType = $this->createTypeMock(); + + $this->registry->addType($expectedType); + $this->assertSame($expectedType, $this->registry->getType('foo')); + } + + public function testAddColumnAlreadyExists() + { + $this->expectException(ColumnAlreadyExistsException::class); + + $type = $this->createColumnTypeMock(); + + $this->registry->addColumn($type); + $this->registry->addColumn($type); + } + + public function testAddColumnType() + { + $this->assertFalse($this->registry->hasColumn('type')); + $this->registry->addColumn($this->createColumnTypeMock()); + $this->assertTrue($this->registry->hasColumn('type')); + } + + public function testAddColumnTypeIsFluent() + { + $registry = $this->registry->addColumn($this->createColumnTypeMock()); + $this->assertSame($registry, $this->registry); + } + + public function testGetColumnTypeUnknown() + { + $this->expectException(ColumnNotFoundException::class); + $this->registry->getColumn('type'); + } + + public function testGetColumnType() + { + $expectedColumnType = $this->createColumnTypeMock(); + + $this->registry->addColumn($expectedColumnType); + $this->assertSame($expectedColumnType, $this->registry->getColumn('type')); + } + + protected function setUp(): void + { + $this->registry = new GridRegistry(); + } + + protected function createTypeMock() + { + $mock = $this->createMock(GridTypeInterface::class); + $mock->expects($this->any()) + ->method('getName') + ->willReturn('foo'); + + return $mock; + } + + protected function createColumnTypeMock() + { + $mock = $this->createMock(Column::class); + $mock->expects($this->any()) + ->method('getType') + ->willReturn('type'); + + return $mock; + } +} diff --git a/Tests/Grid/GridTest.php b/Tests/Grid/GridTest.php new file mode 100644 index 00000000..4b93b6af --- /dev/null +++ b/Tests/Grid/GridTest.php @@ -0,0 +1,6115 @@ +arrange(); + + $column = $this->stubColumn(); + $this->grid->addColumn($column); + + $this->grid->initialize(); + + $this->assertEquals(false, $this->grid->getPersistence()); + $this->assertEmpty($this->grid->getRouteParameters()); + $this->assertEmpty($this->grid->getRouteUrl()); + $this->assertEmpty($this->grid->getSource()); + $this->assertEmpty($this->grid->getDefaultOrder()); + $this->assertEmpty($this->grid->getLimits()); + $this->assertEmpty($this->grid->getMaxResults()); + $this->assertEmpty($this->grid->getPage()); + + $this->router->expects($this->never())->method($this->anything()); + $column->expects($this->never())->method($this->anything()); + } + + public function testInitializePersistence() + { + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('isPersisted') + ->willReturn(true); + + $this->arrange($gridConfig); + + $this->grid->initialize(); + + $this->assertEquals(true, $this->grid->getPersistence()); + } + + public function testInitializeRouteParams() + { + $routeParams = ['foo' => 1, 'bar' => 2]; + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getRouteParameters') + ->willReturn($routeParams); + + $this->arrange($gridConfig); + + $this->grid->initialize(); + + $this->assertEquals($routeParams, $this->grid->getRouteParameters()); + } + + public function testInitializeRouteUrlWithoutParams() + { + $route = 'vendor.bundle.controller.route_name'; + $routeParams = ['foo' => 1, 'bar' => 2]; + $url = 'aRandomUrl'; + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getRouteParameters') + ->willReturn($routeParams); + $gridConfig + ->method('getRoute') + ->willReturn($route); + + $this->arrange($gridConfig); + + $this + ->router + ->method('generate') + ->with($route, $routeParams) + ->willReturn($url); + + $this->grid->initialize(); + + $this->assertEquals($url, $this->grid->getRouteUrl()); + } + + public function testInitializeRouteUrlWithParams() + { + $route = 'vendor.bundle.controller.route_name'; + $url = 'aRandomUrl'; + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getRoute') + ->willReturn($route); + + $this->arrange($gridConfig); + $this + ->router + ->method('generate') + ->with($route, []) + ->willReturn($url); + + $this->grid->initialize(); + + $this->assertEquals($url, $this->grid->getRouteUrl()); + } + + public function testInizializeColumnsNotFilterableAsGridIsNotFilterable() + { + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('isFilterable') + ->willReturn(false); + + $column = $this->stubColumn(); + + $this->arrange($gridConfig); + $this->grid->addColumn($column); + + $column + ->expects($this->atLeastOnce()) + ->method('setFilterable') + ->with(false); + + $this->grid->initialize(); + } + + public function testInizializeColumnsNotSortableAsGridIsNotSortable() + { + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('isSortable') + ->willReturn(false); + + $column = $this->stubColumn(); + + $this->arrange($gridConfig); + $this->grid->addColumn($column); + + $column + ->expects($this->atLeastOnce()) + ->method('setSortable') + ->with(false); + + $this->grid->initialize(); + } + + public function testInitializeNotEntitySource() + { + $source = $this->createMock(Source::class); + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getSource') + ->willReturn($source); + + $this->arrange($gridConfig); + + $source + ->expects($this->once()) + ->method('initialise') + ->with($this->container); + + $this->grid->initialize(); + } + + public function testInitializeEntitySourceWithoutGroupByFunction() + { + $source = $this->createMock(Entity::class); + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getSource') + ->willReturn($source); + + $this->arrange($gridConfig); + + $source + ->expects($this->once()) + ->method('initialise') + ->with($this->container); + $source + ->expects($this->never()) + ->method('setGroupBy'); + + $this->grid->initialize(); + } + + public function testInitializeEntitySourceWithoutGroupByScalarValue() + { + $groupByField = 'groupBy'; + + $source = $this->createMock(Entity::class); + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getSource') + ->willReturn($source); + $gridConfig + ->method('getGroupBy') + ->willReturn($groupByField); + + $this->arrange($gridConfig); + + $source + ->expects($this->once()) + ->method('initialise') + ->with($this->container); + $source + ->expects($this->atLeastOnce()) + ->method('setGroupBy') + ->with([$groupByField]); + + $this->grid->initialize(); + } + + public function testInitializeEntitySourceWithoutGroupByArrayValues() + { + $groupByArray = ['groupByFoo', 'groupByBar']; + + $source = $this->createMock(Entity::class); + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getSource') + ->willReturn($source); + $gridConfig + ->method('getGroupBy') + ->willReturn($groupByArray); + + $this->arrange($gridConfig); + + $source + ->expects($this->once()) + ->method('initialise') + ->with($this->container); + $source + ->expects($this->atLeastOnce()) + ->method('setGroupBy') + ->with($groupByArray); + + $this->grid->initialize(); + } + + public function testInizializeDefaultOrder() + { + $sortBy = 'SORTBY'; + $orderBy = 'ORDERBY'; + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getSortBy') + ->willReturn($sortBy); + $gridConfig + ->method('getOrder') + ->willReturn($orderBy); + + $this->arrange($gridConfig); + + $this->grid->initialize(); + + $this->assertEquals(sprintf('%s|%s', $sortBy, strtolower($orderBy)), $this->grid->getDefaultOrder()); + } + + public function testInizializeDefaultOrderWithoutOrder() + { + $sortBy = 'SORTBY'; + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getSortBy') + ->willReturn($sortBy); + + $this->arrange($gridConfig); + + $this->grid->initialize(); + + // @todo: is this an admitted case? + $this->assertEquals("$sortBy|", $this->grid->getDefaultOrder()); + } + + public function testInizializeLimits() + { + $maxPerPage = 10; + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getMaxPerPage') + ->willReturn($maxPerPage); + + $this->arrange($gridConfig); + + $this->grid->initialize(); + + $this->assertEquals([$maxPerPage => (string) $maxPerPage], $this->grid->getLimits()); + } + + public function testInizializeMaxResults() + { + $maxResults = 50; + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getMaxResults') + ->willReturn($maxResults); + + $this->arrange($gridConfig); + + $this->grid->initialize(); + + $this->assertEquals($maxResults, $this->grid->getMaxResults()); + } + + public function testInizializePage() + { + $page = 1; + + $gridConfig = $this->createMock(GridConfigInterface::class); + $gridConfig + ->method('getPage') + ->willReturn($page); + + $this->arrange($gridConfig); + + $this->grid->initialize(); + + $this->assertEquals($page, $this->grid->getPage()); + } + + public function testSetSourceOneThanOneTime() + { + $source = $this->createMock(Source::class); + + // @todo maybe this exception should not be \InvalidArgumentException? + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(Grid::SOURCE_ALREADY_SETTED_EX_MSG); + + $this->grid->setSource($source); + $this->grid->setSource($source); + } + + public function testSetSource() + { + $source = $this->createMock(Source::class); + + $source + ->expects($this->once()) + ->method('initialise') + ->with($this->container); + $source + ->expects($this->once()) + ->method('getColumns') + ->with($this->isInstanceOf(Columns::class)); + + $this->grid->setSource($source); + + $this->assertEquals($source, $this->grid->getSource()); + } + + public function testGetSource() + { + $source = $this->createMock(Source::class); + + $this->grid->setSource($source); + + $this->assertEquals($source, $this->grid->getSource()); + } + + // public function testGetNullHashIfNotCreated() + // { + // $this->assertNull($this->grid->getHash()); + // } + + public function testHandleRequestRaiseExceptionIfSourceNotSetted() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage(Grid::SOURCE_NOT_SETTED_EX_MSG); + + $this->grid->handleRequest( + $this->getMockBuilder(Request::class) + ->disableOriginalConstructor() + ->getMock() + ); + } + + public function testAddColumnToLazyColumnsWithoutPosition() + { + $column = $this->stubColumn(); + $this->grid->addColumn($column); + + $this->assertEquals([['column' => $column, 'position' => 0]], $this->grid->getLazyAddColumn()); + } + + public function testAddColumnToLazyColumnsWithPosition() + { + $column = $this->stubColumn(); + $this->grid->addColumn($column, 1); + + $this->assertEquals([['column' => $column, 'position' => 1]], $this->grid->getLazyAddColumn()); + } + + public function testAddColumnsToLazyColumnsWithSamePosition() + { + $column1 = $this->stubColumn(); + $column2 = $this->stubColumn(); + + $this->grid->addColumn($column1, 1); + $this->grid->addColumn($column2, 1); + + $this->assertEquals( + [ + ['column' => $column1, 'position' => 1], + ['column' => $column2, 'position' => 1], ], + $this->grid->getLazyAddColumn() + ); + } + + public function testGetColumnFromLazyColumns() + { + $columnId = 'foo'; + $column = $this->stubColumn($columnId); + + $this->grid->addColumn($column); + + $this->assertEquals($column, $this->grid->getColumn($columnId)); + } + + public function testGetColumnFromColumns() + { + $columnId = 'foo'; + $column = $this->stubColumn(); + + $columns = $this->createMock(Columns::class); + $columns + ->method('getColumnById') + ->with($columnId) + ->willReturn($column); + + $this->grid->setColumns($columns); + + $this->assertEquals($column, $this->grid->getColumn($columnId)); + } + + public function testRaiseExceptionIfGetNonExistentColumn() + { + $columnId = 'foo'; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf(Columns::MISSING_COLUMN_EX_MSG, $columnId)); + + $this->grid->getColumn($columnId); + } + + public function testGetColumns() + { + $this->assertInstanceOf(Columns::class, $this->grid->getColumns()); + } + + public function testHasColumnInLazyColumns() + { + $columnId = 'foo'; + $column = $this->stubColumn($columnId); + $this->grid->addColumn($column); + + $this->assertTrue($this->grid->hasColumn($columnId)); + } + + public function testHasColumnInColumns() + { + $columnId = 'foo'; + + $columns = $this->createMock(Columns::class); + $columns + ->method('hasColumnById') + ->with($columnId) + ->willReturn(true); + + $this->grid->setColumns($columns); + + $this->assertTrue($this->grid->hasColumn($columnId)); + } + + public function testSetColumns() + { + $columns = $this->createMock(Columns::class); + $this->grid->setColumns($columns); + + $this->assertEquals($columns, $this->grid->getColumns()); + } + + public function testColumnsReorderAndKeepOtherColumns() + { + $ids = ['col1', 'col3', 'col2']; + + $columns = $this->createMock(Columns::class); + $columns + ->expects($this->once()) + ->method('setColumnsOrder') + ->with($ids, true); + + $this->grid->setColumns($columns); + + $this->grid->setColumnsOrder($ids, true); + } + + public function testColumnsReorderAndDontKeepOtherColumns() + { + $ids = ['col1', 'col3', 'col2']; + + $columns = $this->createMock(Columns::class); + $columns + ->expects($this->once()) + ->method('setColumnsOrder') + ->with($ids, false); + + $this->grid->setColumns($columns); + + $this->grid->setColumnsOrder($ids, false); + } + + public function testAddMassActionWithoutRole() + { + $massAction = $this->stubMassAction(); + $this->grid->addMassAction($massAction); + + $this->assertEquals([$massAction], $this->grid->getMassActions()); + } + + public function testAddMassActionWithGrantForActionRole() + { + $role = 'aRole'; + $massAction = $this->stubMassAction($role); + + $this + ->authChecker + ->method('isGranted') + ->with($role) + ->willReturn(true); + + $this->grid->addMassAction($massAction); + + $this->assertEquals([$massAction], $this->grid->getMassActions()); + } + + public function testAddMassActionWithoutGrantForActionRole() + { + $role = 'aRole'; + $massAction = $this->stubMassAction($role); + + $this + ->authChecker + ->method('isGranted') + ->with($role) + ->willReturn(false); + + $this->grid->addMassAction($massAction); + + $this->assertEmpty($this->grid->getMassActions()); + } + + public function testGetMassActions() + { + $massAction = $this->stubMassAction(); + $this->grid->addMassAction($massAction); + + $this->assertEquals([$massAction], $this->grid->getMassActions()); + } + + public function testRaiseExceptionIfAddTweakWithNotValidId() + { + $tweakId = '#tweakNotValidId'; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf(Grid::TWEAK_MALFORMED_ID_EX_MSG, $tweakId)); + + $this->grid->addTweak('title', [], $tweakId); + } + + public function testAddTweakWithId() + { + $title = 'aTweak'; + $tweak = ['filters' => [], 'order' => 'columnId', 'page' => 1, 'limit' => 50, 'export' => 1, 'massAction' => 1]; + $id = 'aValidTweakId'; + $group = 'tweakGroup'; + + $this->grid->addTweak($title, $tweak, $id, $group); + + $result = [$id => array_merge(['title' => $title, 'id' => $id, 'group' => $group], $tweak)]; + + $this->assertEquals($result, $this->grid->getRawTweaks()); + } + + public function testAddTweakWithoutId() + { + $title = 'aTweak'; + $tweak = ['filters' => [], 'order' => 'columnId', 'page' => 1, 'limit' => 50, 'export' => 1, 'massAction' => 1]; + $group = 'tweakGroup'; + + $this->grid->addTweak($title, $tweak, null, $group); + + $result = [0 => array_merge(['title' => $title, 'id' => null, 'group' => $group], $tweak)]; + + $this->assertEquals($result, $this->grid->getRawTweaks()); + } + + public function testAddRowActionWithoutRole() + { + $colId = 'aColId'; + $rowAction = $this->stubRowAction(null, $colId); + $this->grid->addRowAction($rowAction); + + $this->assertEquals([$colId => [$rowAction]], $this->grid->getRowActions()); + } + + public function testAddRowActionWithGrantForActionRole() + { + $role = 'aRole'; + $colId = 'aColId'; + $rowAction = $this->stubRowAction($role, $colId); + + $this + ->authChecker + ->method('isGranted') + ->with($role) + ->willReturn(true); + + $this->grid->addRowAction($rowAction); + + $this->assertEquals([$colId => [$rowAction]], $this->grid->getRowActions()); + } + + public function testAddRowActionWithoutGrantForActionRole() + { + $role = 'aRole'; + $rowAction = $this->stubRowAction($role); + + $this + ->authChecker + ->method('isGranted') + ->with($role) + ->willReturn(false); + + $this->grid->addRowAction($rowAction); + + $this->assertEmpty($this->grid->getRowActions()); + } + + public function testGetRowActions() + { + $colId = 'aColId'; + $rowAction = $this->stubRowAction(null, $colId); + $this->grid->addRowAction($rowAction); + + $this->assertEquals([$colId => [$rowAction]], $this->grid->getRowActions()); + } + + public function testSetExportTwigTemplateInstance() + { + $templateName = 'templateName'; + + $env = $this->createMock(Environment::class); + $template = $this->createMock(Template::class); + + $template + ->method('getTemplateName') + ->willReturn($templateName); + $wrapper = new TemplateWrapper($env, $template); + + $result = '__SELF__' . $templateName; + + // $this + // ->session + // ->expects($this->once()) + // ->method('set') + // ->with($this->anything(), [Grid::REQUEST_QUERY_TEMPLATE => $result]); + + $this->grid->setTemplate($wrapper); + } + + public function testSetExportStringTemplate() + { + $template = 'templateString'; + + $this + ->session + ->expects($this->once()) + ->method('set') + ->with($this->anything(), [Grid::REQUEST_QUERY_TEMPLATE => $template]); + + + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + $this->grid->handleRequest($this->request); + $this->grid->setTemplate($template); + } + + public function testRaiseExceptionIfSetTemplateWithNoValidValue() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage(Grid::TWIG_TEMPLATE_LOAD_EX_MSG); + + $this + ->session + ->expects($this->never()) + ->method('set') + ->with($this->anything(), $this->anything()); + + $this->grid->setTemplate(true); + } + + public function testSetExportNullTemplate() + { + $this + ->session + ->expects($this->never()) + ->method('set') + ->with($this->anything(), $this->anything()); + + $this->grid->setTemplate(null); + } + + public function testReturnTwigTemplate() + { + $templateName = 'templateName'; + + $env = $this->createMock(Environment::class); + $template = $this->createMock(Template::class); + + $template + ->method('getTemplateName') + ->willReturn($templateName); + $wrapper = new TemplateWrapper($env, $template); + + $result = '__SELF__' . $templateName; + + $this->grid->setTemplate($wrapper); + + $this->assertEquals($result, $this->grid->getTemplate()); + } + + public function testReturnStringTemplate() + { + $template = 'templateString'; + + $this->grid->setTemplate($template); + + $this->assertEquals($template, $this->grid->getTemplate()); + } + + public function testAddExportWithoutRole() + { + $export = $this->createMock(ExportInterface::class); + $export + ->method('getRole') + ->willReturn(null); + + $this->grid->addExport($export); + + $this->assertEquals([$export], $this->grid->getExports()); + } + + public function testAddExportWithGrantForActionRole() + { + $role = 'aRole'; + + $export = $this->createMock(ExportInterface::class); + $export + ->method('getRole') + ->willReturn($role); + + $this + ->authChecker + ->method('isGranted') + ->with($role) + ->willReturn(true); + + $this->grid->addExport($export); + + $this->assertEquals([$export], $this->grid->getExports()); + } + + public function testAddExportWithoutGrantForActionRole() + { + $role = 'aRole'; + + $export = $this->createMock(ExportInterface::class); + $export + ->method('getRole') + ->willReturn($role); + + $this + ->authChecker + ->method('isGranted') + ->with($role) + ->willReturn(false); + + $this->grid->addExport($export); + + $this->assertEmpty($this->grid->getExports()); + } + + public function testGetExports() + { + $export = $this->createMock(ExportInterface::class); + $export + ->method('getRole') + ->willReturn(null); + + $this->grid->addExport($export); + + $this->assertEquals([$export], $this->grid->getExports()); + } + + public function testSetRouteParameter() + { + $paramName = 'name'; + $paramValue = 'value'; + + $otherParamName = 'name'; + $otherParamValue = 'value'; + + $this->grid->setRouteParameter($paramName, $paramValue); + $this->grid->setRouteParameter($otherParamName, $otherParamValue); + + $this->assertEquals( + [$paramName => $paramValue, $otherParamName => $otherParamValue], + $this->grid->getRouteParameters() + ); + } + + public function testGetRouteParameters() + { + $paramName = 'name'; + $paramValue = 'value'; + + $otherParamName = 'name'; + $otherParamValue = 'value'; + + $this->grid->setRouteParameter($paramName, $paramValue); + $this->grid->setRouteParameter($otherParamName, $otherParamValue); + + $this->assertEquals( + [$paramName => $paramValue, $otherParamName => $otherParamValue], + $this->grid->getRouteParameters() + ); + } + + public function testSetRouteUrl() + { + $url = 'url'; + + $this->grid->setRouteUrl($url); + + $this->assertEquals($url, $this->grid->getRouteUrl()); + } + + public function testGetRouteUrl() + { + $url = 'url'; + + $this->grid->setRouteUrl($url); + + $this->assertEquals($url, $this->grid->getRouteUrl()); + } + + public function testGetRouteUrlFromRequest() + { + $url = 'url'; + + $this + ->request + ->method('get') + ->with('_route') + ->willReturn($url); + + $this + ->router + ->method('generate') + ->with($url, $this->anything()) + ->willReturn($url); + + $this->assertEquals($url, $this->grid->getRouteUrl()); + } + + public function testSetId() + { + $id = 'id'; + $this->grid->setId($id); + + $this->assertEquals($id, $this->grid->getId()); + } + + public function testGetId() + { + $id = 'id'; + $this->grid->setId($id); + + $this->assertEquals($id, $this->grid->getId()); + } + + public function testSetPersistence() + { + $this->grid->setPersistence(true); + + $this->assertEquals(true, $this->grid->getPersistence()); + } + + public function testGetPersistence() + { + $this->grid->setPersistence(true); + + $this->assertTrue($this->grid->getPersistence()); + } + + public function testSetDataJunction() + { + $this->grid->setDataJunction(Column::DATA_DISJUNCTION); + + $this->assertEquals(Column::DATA_DISJUNCTION, $this->grid->getDataJunction()); + } + + public function testGetDataJunction() + { + $this->grid->setDataJunction(Column::DATA_DISJUNCTION); + + $this->assertEquals(Column::DATA_DISJUNCTION, $this->grid->getDataJunction()); + } + + public function testSetInvalidLimitsRaiseException() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(Grid::NOT_VALID_LIMIT_EX_MSG); + + $this->grid->setLimits('foo'); + } + + public function testSetIntLimit() + { + $limit = 10; + $this->grid->setLimits($limit); + + $this->assertEquals([$limit => (string) $limit], $this->grid->getLimits()); + } + + public function testSetArrayLimits() + { + $limits = [10, 50, 100]; + $this->grid->setLimits($limits); + + $this->assertEquals(array_combine($limits, $limits), $this->grid->getLimits()); + } + + public function testSetAssociativeArrayLimits() + { + $limits = [10 => '10', 50 => '50', 100 => '100']; + $this->grid->setLimits($limits); + + $this->assertEquals(array_combine($limits, $limits), $this->grid->getLimits()); + } + + public function testGetLimits() + { + $limits = [10, 50, 100]; + $this->grid->setLimits($limits); + + $this->assertEquals(array_combine($limits, $limits), $this->grid->getLimits()); + } + + public function testSetDefaultPage() + { + $page = 1; + $this->grid->setDefaultPage($page); + + $this->assertEquals($page - 1, $this->grid->getPage()); + } + + public function testSetDefaultTweak() + { + $tweakId = 1; + $this->grid->setDefaultTweak($tweakId); + + $this->assertEquals($tweakId, $this->grid->getDefaultTweak()); + } + + public function testSetPageWithInvalidValueRaiseException() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(Grid::NOT_VALID_PAGE_NUMBER_EX_MSG); + + $page = '-1'; + $this->grid->setPage($page); + } + + public function testSetPageWithZeroValue() + { + $page = 0; + $this->grid->setPage($page); + + $this->assertEquals($page, $this->grid->getPage()); + } + + public function testSetPage() + { + $page = 10; + $this->grid->setPage($page); + + $this->assertEquals($page, $this->grid->getPage()); + } + + public function testGetPage() + { + $page = 10; + $this->grid->setPage($page); + + $this->assertEquals($page, $this->grid->getPage()); + } + + public function testSetMaxResultWithNullValue() + { + $this->grid->setMaxResults(); + $this->assertEquals(null, $this->grid->getMaxResults()); + } + + public function testSetMaxResultWithInvalidValueRaiseException() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(Grid::NOT_VALID_MAX_RESULT_EX_MSG); + + $this->grid->setMaxResults(-1); + } + + // @todo: has this case sense? Should not raise exception? + public function testSetMaxResultWithStringValue() + { + $maxResult = 'foo'; + $this->grid->setMaxResults($maxResult); + + $this->assertEquals($maxResult, $this->grid->getMaxResults()); + } + + public function testSetMaxResult() + { + $maxResult = 1; + $this->grid->setMaxResults($maxResult); + + $this->assertEquals($maxResult, $this->grid->getMaxResults()); + } + + public function testIsNotFilteredIfNoColumnIsFiltered() + { + $column1 = $this->stubColumn(); + $column2 = $this->stubColumn(); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column1); + $columns->addColumn($column2); + + $this->grid->setColumns($columns); + + $this->assertFalse($this->grid->isFiltered()); + } + + public function testIsFilteredIfAtLeastAColumnIsFiltered() + { + $column1 = $this->stubColumn(); + $column2 = $this->stubFilteredColumn(); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column1); + $columns->addColumn($column2); + + $this->grid->setColumns($columns); + + $this->assertTrue($this->grid->isFiltered()); + } + + public function testShowTitlesIfAtLeastOneColumnHasATitle() + { + $column1 = $this->stubColumn(); + $column2 = $this->stubTitledColumn(); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column1); + $columns->addColumn($column2); + + $this->grid->setColumns($columns); + + $this->assertTrue($this->grid->isTitleSectionVisible()); + } + + public function testDontShowTitlesIfNoColumnsHasATitle() + { + $column1 = $this->stubColumn(); + $column2 = $this->stubColumn(); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column1); + $columns->addColumn($column2); + + $this->grid->setColumns($columns); + + $this->assertFalse($this->grid->isTitleSectionVisible()); + } + + public function testDontShowTitles() + { + $column = $this->stubTitledColumn(); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column); + + $this->grid->setColumns($columns); + + $this->grid->hideTitles(); + $this->assertFalse($this->grid->isTitleSectionVisible()); + } + + public function testShowFilterSectionIfAtLeastOneColumnFilterable() + { + $column1 = $this->stubColumn(); + $column2 = $this->stubFilterableColumn('text'); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column1); + $columns->addColumn($column2); + + $this->grid->setColumns($columns); + + $this->assertTrue($this->grid->isFilterSectionVisible()); + } + + public function testDontShowFilterSectionIfColumnVisibleTypeIsMassAction() + { + $column = $this->stubFilterableColumn('massaction'); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column); + + $this->grid->setColumns($columns); + + $this->assertFalse($this->grid->isFilterSectionVisible()); + } + + public function testDontShowFilterSectionIfColumnVisibleTypeIsActions() + { + $column = $this->stubFilterableColumn('actions'); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column); + + $this->grid->setColumns($columns); + + $this->assertFalse($this->grid->isFilterSectionVisible()); + } + + public function testDontShowFilterSectionIfNoColumnFilterable() + { + $column1 = $this->stubColumn(); + $column2 = $this->stubColumn(); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column1); + $columns->addColumn($column2); + + $this->grid->setColumns($columns); + + $this->assertFalse($this->grid->isFilterSectionVisible()); + } + + public function testDontShowFilterSection() + { + $this->grid->hideFilters(); + + $this->assertFalse($this->grid->isFilterSectionVisible()); + } + + public function testHideFilters() + { + $this->grid->hideFilters(); + + $this->assertEquals(false, $this->grid->getShowFilters()); + } + + public function testHideTitles() + { + $this->grid->hideTitles(); + + $this->assertEquals(false, $this->grid->getShowTitles()); + } + + public function testAddsColumnExtension() + { + $extension = $this->stubColumn(); + + $columns = $this + ->getMockBuilder(Columns::class) + ->disableOriginalConstructor() + ->getMock(); + $columns + ->expects($this->once()) + ->method('addExtension') + ->with($extension); + + $this->grid->setColumns($columns); + + $this->grid->addColumnExtension($extension); + } + + public function testSetPrefixTitle() + { + $prefixTitle = 'prefixTitle'; + $this->grid->setPrefixTitle($prefixTitle); + + $this->assertEquals($prefixTitle, $this->grid->getPrefixTitle()); + } + + public function testGetPrefixTitle() + { + $prefixTitle = 'prefixTitle'; + $this->grid->setPrefixTitle($prefixTitle); + + $this->assertEquals($prefixTitle, $this->grid->getPrefixTitle()); + } + + public function testSetNoDataMessage() + { + $message = 'foo'; + $this->grid->setNoDataMessage($message); + + $this->assertEquals($message, $this->grid->getNoDataMessage()); + } + + public function testGetNoDataMessage() + { + $message = 'foo'; + $this->grid->setNoDataMessage($message); + + $this->assertEquals($message, $this->grid->getNoDataMessage()); + } + + public function testSetNoResultMessage() + { + $message = 'foo'; + $this->grid->setNoResultMessage($message); + + $this->assertEquals($message, $this->grid->getNoResultMessage()); + } + + public function testGetNoResultMessage() + { + $message = 'foo'; + $this->grid->setNoResultMessage($message); + + $this->assertEquals($message, $this->grid->getNoResultMessage()); + } + + public function testSetHiddenColumnsWithIntegerId() + { + $id = 1; + $this->grid->setHiddenColumns($id); + + $this->assertEquals([$id], $this->grid->getLazyHiddenColumns()); + } + + public function testSetHiddenColumnWithArrayOfIds() + { + $ids = [1, 2, 3]; + $this->grid->setHiddenColumns($ids); + + $this->assertEquals($ids, $this->grid->getLazyHiddenColumns()); + } + + public function testSetVisibleColumnsWithIntegerId() + { + $id = 1; + $this->grid->setVisibleColumns($id); + + $this->assertEquals([$id], $this->grid->getLazyVisibleColumns()); + } + + public function testSetVisibleColumnWithArrayOfIds() + { + $ids = [1, 2, 3]; + $this->grid->setVisibleColumns($ids); + + $this->assertEquals($ids, $this->grid->getLazyVisibleColumns()); + } + + public function testShowColumnsWithIntegerId() + { + $id = 1; + $this->grid->showColumns($id); + + $this->assertEquals([$id => true], $this->grid->getLazyHideShowColumns()); + } + + public function testShowColumnsArrayOfIds() + { + $ids = [1, 2, 3]; + $this->grid->showColumns($ids); + + $this->assertEquals([1 => true, 2 => true, 3 => true], $this->grid->getLazyHideShowColumns()); + } + + public function testHideColumnsWithIntegerId() + { + $id = 1; + $this->grid->hideColumns($id); + + $this->assertEquals([$id => false], $this->grid->getLazyHideShowColumns()); + } + + public function testHideColumnsArrayOfIds() + { + $ids = [1, 2, 3]; + $this->grid->hideColumns($ids); + + $this->assertEquals([1 => false, 2 => false, 3 => false], $this->grid->getLazyHideShowColumns()); + } + + public function testSetActionsColumnSize() + { + $size = 2; + $this->grid->setActionsColumnSize($size); + + $this->assertEquals($size, $this->grid->getActionsColumnSize()); + } + + public function testSetActionsColumnTitle() + { + $title = 'aTitle'; + $this->grid->setActionsColumnTitle($title); + + $this->assertEquals($title, $this->grid->getActionsColumnTitle()); + } + + public function testClone() + { + $column1 = $this->stubColumn(); + $column2 = $this->stubColumn(); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column1); + $columns->addColumn($column2); + + $this->grid->setColumns($columns); + $grid = clone $this->grid; + + $this->assertNotSame($columns, $grid->getColumns()); + } + + public function testRaiseExceptionDuringHandleRequestIfNoSourceSetted() + { + $this->expectException(\LogicException::class); + $this->expectExceptionMessage(Grid::SOURCE_NOT_SETTED_EX_MSG); + + $request = $this + ->getMockBuilder(Request::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->grid->handleRequest($request); + } + + public function testCreateHashWithIdDuringHandleRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->grid->handleRequest($this->request); + + $this->assertEquals($this->gridHash, $this->grid->getHash()); + } + + public function testCreateHashWithMd5DuringHandleRequest() + { + $this->arrange($this->createMock(GridConfigInterface::class), null); + + $sourceHash = '4f403d7e887f7d443360504a01aaa30e'; + + $this->arrangeGridSourceDataLoadedWithEmptyRows(0, $sourceHash); + + $column = $this->stubPrimaryColumn(); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column); + $this->grid->setColumns($columns); + + $controller = 'aController'; + + $this + ->request + ->expects($this->at(1)) + ->method('get') + ->with('_controller') + ->willReturn($controller); + + $this->grid->handleRequest($this->request); + + $this->assertEquals('grid_' . md5($controller . $columns->getHash() . $sourceHash), $this->grid->getHash()); + } + + public function testResetGridSessionWhenChangeGridDuringHandleRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this + ->request + ->headers + ->method('get') + ->with('referer') + ->willReturn('previousGrid'); + + $this + ->session + ->expects($this->once()) + ->method('remove') + ->with($this->gridHash); + + $this->grid->handleRequest($this->request); + } + + public function testResetGridSessionWhenResetFiltersIsPressedDuringHandleRequest() + { + $this->mockResetGridSessionWhenResetFilterIsPressed(); + + $this->grid->handleRequest($this->request); + } + + public function testNotResetGridSessionWhenXmlHttpRequestDuringHandleRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this + ->request + ->method('isXmlHttpRequest') + ->willReturn(true); + + $this + ->session + ->expects($this->never()) + ->method('remove') + ->with($this->gridHash); + + $this->grid->handleRequest($this->request); + } + + public function testNotResetGridSessionWhenPersistenceSettedDuringHandleRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this + ->request + ->method('isXmlHttpRequest') + ->willReturn(true); + + $this + ->session + ->expects($this->never()) + ->method('remove') + ->with($this->gridHash); + + $this->grid->setPersistence(true); + + $this->grid->handleRequest($this->request); + } + + public function testNotResetGridSessionWhenRefererIsSameGridDuringHandleRequest() + { + $this->mockNotResetGridSessionWhenSameGridReferer(); + + $this->grid->handleRequest($this->request); + } + + public function testStartNewSessionDuringHandleRequestOnFirstGridRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->grid->handleRequest($this->request); + + $this->assertEquals(true, $this->grid->getNewSession()); + } + + public function testStartKeepSessionDuringHandleRequestNotOnFirstGridRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this + ->session + ->method('get') + ->with($this->gridHash) + ->willReturn('sessionData'); + + $this->grid->handleRequest($this->request); + + $this->assertEquals(false, $this->grid->getNewSession()); + } + + public function testMassActionRedirect() + { + $this->mockMassActionCallbackResponse(); + + $this->grid->handleRequest($this->request); + + $this->assertTrue($this->grid->isMassActionRedirect()); + } + + public function testRaiseExceptionIfMassActionIdNotValidDuringHandleRequest() + { + $massActionId = 10; + + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage(sprintf(Grid::MASS_ACTION_NOT_DEFINED_EX_MSG, $massActionId)); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_MASS_ACTION => $massActionId]); + + $this->grid->handleRequest($this->request); + } + + public function testRaiseExceptionIfMassActionCallbackNotValidDuringHandleRequest() + { + $invalidCallback = 'invalidCallback'; + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage(sprintf(Grid::MASS_ACTION_CALLBACK_NOT_VALID_EX_MSG, $invalidCallback)); + + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_MASS_ACTION => 0]); + + $massAction = $this->stubMassActionWithCallback($invalidCallback); + + $this->grid->addMassAction($massAction); + + $this->grid->handleRequest($this->request); + } + + public function testResetPageAndLimitIfMassActionHandleAllDataDuringHandleRequest() + { + $this->mockResetPageAndLimitIfMassActionAndAllKeys(); + + $this->grid->handleRequest($this->request); + + $this->assertEquals(0, $this->grid->getLimit()); + } + + public function testMassActionResponseFromCallbackDuringHandleRequest() + { + $callbackResponse = $this->mockMassActionCallbackResponse(); + + $this->grid->handleRequest($this->request); + + $this->assertEquals($callbackResponse, $this->grid->getMassActionResponse()); + } + + public function testMassActionResponseFromControllerActionDuringHandleRequest() + { + $callbackResponse = $this->mockMassActionControllerResponse(); + + $this->grid->handleRequest($this->request); + + $this->assertEquals($callbackResponse, $this->grid->getMassActionResponse()); + } + + public function testRaiseExceptionIfExportIdNotValidDuringHandleRequest() + { + $exportId = 10; + + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage(sprintf(Grid::EXPORT_NOT_DEFINED_EX_MSG, $exportId)); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_EXPORT => $exportId]); + + $this->grid->handleRequest($this->request); + } + + public function testProcessExportsDuringHandleRequest() + { + $response = $this->mockExports(); + + $this->grid->handleRequest($this->request); + + $this->assertEquals(0, $this->grid->getPage()); + $this->assertEquals(0, $this->grid->getLimit()); + $this->assertEquals(true, $this->grid->isReadyForExport()); + $this->assertEquals($response, $this->grid->getExportResponse()); + } + + public function testProcessExportsButNotFiltersPageOrderLimitDuringHandleRequest() + { + $this->mockExportsButNotFiltersPageOrderLimit(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessPageDuringHandleRequest() + { + $this->mockPageRequestData(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessPageWithQueryOrderingDuringHandleRequest() + { + $this->mockPageQueryOrderRequestData(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessPageWithQueryLimitDuringHandleRequest() + { + $this->mockPageLimitRequestData(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessPageWithMassActionDuringHandleRequest() + { + $this->mockPageMassActionRequestData(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessPageWithFiltersAndRequestDataDuringHandleRequest() + { + $this->mockPageFiltersRequestData(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessPageWithFiltersDifferentFromSelectDuringHandleRequest() + { + $this->mockPageNotSelectFilterRequestData(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessPageWithSelectFilterColumnNotSelectMultiDuringHandleRequest() + { + $this->mockPageColumnNotSelectMultiRequestData(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessOrderDescDuringHandleRequest() + { + $colId = 'colId'; + $order = 'desc'; + $queryOrder = "$colId|$order"; + + $column = $this->mockOrderRequestData($colId, $order); + + $column + ->expects($this->once()) + ->method('setOrder') + ->with($order); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_ORDER => $queryOrder, Grid::REQUEST_QUERY_PAGE => 0]); + + $this->grid->handleRequest($this->request); + } + + public function testProcessOrderAscDuringHandleRequest() + { + $colId = 'colId'; + $order = 'asc'; + $queryOrder = "$colId|$order"; + + $column = $this->mockOrderRequestData($colId, $order); + + $column + ->expects($this->once()) + ->method('setOrder') + ->with($order); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_ORDER => $queryOrder, Grid::REQUEST_QUERY_PAGE => 0]); + + $this->grid->handleRequest($this->request); + } + + public function testProcessOrderColumnNotSortableDuringHandleRequest() + { + $this->mockOrderColumnNotSortable(); + + $this->grid->handleRequest($this->request); + } + + public function testColumnsNotOrderedDuringHandleRequestIfNoOrderRequested() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->stubPrimaryColumn(); + $column + ->method('isSortable') + ->willReturn(true); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column); + $this->grid->setColumns($columns); + + $this->stubRequestWithData([]); + + $column + ->expects($this->never()) + ->method('setOrder'); + + $this->grid->handleRequest($this->request); + + $this->assertEquals(0, $this->grid->getPage()); + } + + public function testProcessConfiguredLimitDuringHandleRequest() + { + $this->mockConfiguredLimitRequestData(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessNonConfiguredLimitDuringHandleRequest() + { + $this->mockNonConfiguredLimitRequestData(); + + $this->grid->handleRequest($this->request); + + $this->assertEmpty($this->grid->getLimit()); + } + + public function testSetDefaultSessionFiltersDuringHandleRequest() + { + $this->mockDefaultSessionFiltersWithoutRequestData(); + + $this->grid->handleRequest($this->request); + } + + public function testSetDefaultPageRaiseExceptionIfPageHasNegativeValueDuringHandleRequest() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(Grid::PAGE_NOT_VALID_EX_MSG); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->grid->setDefaultPage(-1); + + $this->grid->handleRequest($this->request); + } + + public function testSetDefaultPageDuringHandleRequest() + { + $this->mockDefaultPage(); + + $this->grid->handleRequest($this->request); + } + + public function testSetDefaultOrderRaiseExceptionIfOrderNotAscNeitherDescDuringHandleRequest() + { + $columnOrder = 'foo'; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf(Grid::COLUMN_ORDER_NOT_VALID_EX_MSG, $columnOrder)); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $colId = 'col'; + $column = $this->stubColumn($colId); + $this->grid->addColumn($column); + + $this->grid->setDefaultOrder($colId, $columnOrder); + + $this->grid->handleRequest($this->request); + } + + public function testSetDefaultOrderRaiseExceptionIfColumnDoesNotExistsDuringHandleRequest() + { + $colId = 'col'; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf(Columns::MISSING_COLUMN_EX_MSG, $colId)); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->arrangeGridPrimaryColumn(); + + $this->grid->setDefaultOrder($colId, 'asc'); + + $this->grid->handleRequest($this->request); + } + + public function testSetDefaultOrderAscDuringHandleRequest() + { + $this->mockDefaultOrder('asc'); + + $this->grid->handleRequest($this->request); + } + + public function testSetDefaultOrderDescDuringHandleRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $columnId = 'columnId'; + $order = 'desc'; + $column + ->method('getId') + ->willReturn($columnId); + + $this->grid->setDefaultOrder($columnId, $order); + + $column + ->expects($this->once()) + ->method('setOrder') + ->with($order); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_ORDER => "$columnId|$order"]); + + $this->grid->handleRequest($this->request); + } + + public function testSetDefaultLimitRaiseExceptionIfLimitIsNotAPositiveDuringHandleRequest() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(Grid::DEFAULT_LIMIT_NOT_VALID_EX_MSG); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->grid->setDefaultLimit(-1); + + $this->grid->handleRequest($this->request); + } + + public function testSetDefaultLimitRaiseExceptionIfLimitIsNotDefinedInGridLimitsDuringHandleRequest() + { + $limit = 2; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf(Grid::LIMIT_NOT_DEFINED_EX_MSG, $limit)); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->grid->setDefaultLimit($limit); + + $this->grid->handleRequest($this->request); + } + + public function testSetDefaultLimitDuringHandleRequest() + { + $this->mockDefaultLimit(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessDefaultTweaksDuringHandleRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $title = 'aTweak'; + $tweak = ['reset' => 1]; + $tweakId = 'aValidTweakId'; + + $this->grid->addTweak($title, $tweak, $tweakId); + + $this->grid->setDefaultTweak($tweakId); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('remove') + ->with($this->gridHash); + + $this->grid->handleRequest($this->request); + } + + public function testSetPermanentSessionFiltersDuringHandleRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $col1Id = 'col1'; + $col1FilterValue = 'val1'; + $column1 = $this->stubColumn($col1Id); + $this->grid->addColumn($column1); + + $col2Id = 'col2'; + $col2FilterValue = ['val2']; + $column2 = $this->stubColumn($col2Id); + $this->grid->addColumn($column2); + + $col3Id = 'col3'; + $col3FilterValue = ['from' => true]; + $column3 = $this->stubColumn($col3Id); + $this->grid->addColumn($column3); + + $col4Id = 'col4'; + $col4FilterValue = ['from' => false]; + $column4 = $this->stubColumn($col4Id); + $this->grid->addColumn($column4); + + $col5Id = 'col5'; + $col5FilterValue = ['from' => 'foo', 'to' => 'bar']; + $column5 = $this + ->getMockBuilder(Column::class) + ->disableOriginalConstructor() + ->getMock(); + $column5 + ->method('getId') + ->willReturn($col5Id); + $column5 + ->method('getFilterType') + ->willReturn('select'); + + $this->grid->addColumn($column5); + + $this->grid->setPermanentFilters([ + $col1Id => $col1FilterValue, + $col2Id => $col2FilterValue, + $col3Id => $col3FilterValue, + $col4Id => $col4FilterValue, + $col5Id => $col5FilterValue, + ]); + + $column + ->expects($this->never()) + ->method('setData') + ->with($this->anything()); + $column1 + ->expects($this->once()) + ->method('setData') + ->with(['from' => $col1FilterValue]); + $column2 + ->expects($this->once()) + ->method('setData') + ->with(['from' => $col2FilterValue]); + $column3 + ->expects($this->once()) + ->method('setData') + ->with(['from' => 1]); + $column4 + ->expects($this->once()) + ->method('setData') + ->with(['from' => 0]); + $column5 + ->expects($this->once()) + ->method('setData') + ->with(['from' => ['foo'], 'to' => ['bar']]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [ + $col1Id => ['from' => $col1FilterValue], + $col2Id => ['from' => $col2FilterValue], + $col3Id => ['from' => 1], + $col4Id => ['from' => 0], + $col5Id => ['from' => ['foo'], 'to' => ['bar']], + ]); + + $this->grid->handleRequest($this->request); + } + + public function testPrepareRowsFromDataIfDataAlreadyLoadedDuringHandleRequest() + { + $source = $this->arrangeGridSourceDataLoadedWithoutRowsReturned(); + $columns = $this->arrangeGridWithColumnsIterator(); + + $maxResults = 5; + $limit = 10; + $this->stubRequestWithData([Grid::REQUEST_QUERY_LIMIT => $limit]); + + $this->grid->setLimits($limit); + $this->grid->setMaxResults($maxResults); + + $source + ->expects($this->once()) + ->method('executeFromData') + ->with($columns->getIterator(), 0, $limit, $maxResults) + ->willReturn(new Rows()); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_LIMIT => $limit, Grid::REQUEST_QUERY_PAGE => 0]); + + $this->grid->handleRequest($this->request); + } + + public function testPrepareRowsFromExecutionIfDataNotLoadedDuringHandleRequest() + { + $source = $this->arrangeGridSourceDataNotLoadedWithoutRowsReturned(); + $columns = $this->arrangeGridWithColumnsIterator(); + + $maxResults = 5; + $limit = 10; + $this->stubRequestWithData([Grid::REQUEST_QUERY_LIMIT => $limit]); + + $this->grid->setLimits($limit); + $this->grid->setMaxResults($maxResults); + $this->grid->setDataJunction(Column::DATA_DISJUNCTION); + + $source + ->expects($this->once()) + ->method('execute') + ->with($columns->getIterator(), 0, $limit, $maxResults, Column::DATA_DISJUNCTION) + ->willReturn(new Rows()); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_LIMIT => $limit, Grid::REQUEST_QUERY_PAGE => 0]); + + $this->grid->handleRequest($this->request); + } + + public function testRaiseExceptionIfNotRowInstanceReturnedFromSurceIfDataAlreadyLoadedDuringHandleRequest() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage(Grid::NO_ROWS_RETURNED_EX_MSG); + + $this->arrangeGridSourceDataLoadedWithoutRowsReturned(); + + $this->grid->handleRequest($this->request); + } + + public function testRaiseExceptionIfNotRowInstanceReturnedFromSurceIfDataNotLoadedLoadedDuringHandleRequest() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage(Grid::NO_ROWS_RETURNED_EX_MSG); + + $this->arrangeGridSourceDataNotLoadedWithoutRowsReturned(); + + $this->grid->handleRequest($this->request); + } + + public function testSetFirstPageIfNoRowsFromSourceIfDataAlreadyDataAndRequestedPageNotFirst() + { + $source = $this->arrangeGridSourceDataLoadedWithoutRowsReturned(); + $columns = $this->arrangeGridWithColumnsIterator(); + + $page = 2; + $this->stubRequestWithData([Grid::REQUEST_QUERY_PAGE => $page]); + + $executeFromDataMap = [ + [$columns->getIterator(), $page, null, null, new Rows()], + [$columns->getIterator(), 0, null, null, new Rows()], + ]; + + $source + ->expects($this->exactly(2)) + ->method('executeFromData') + ->will($this->returnValueMap($executeFromDataMap)); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]); + + $this->grid->handleRequest($this->request); + } + + public function testSetFirstPageIfNoRowsFromSourceIfDataNotLoadedAndRequestedPageNotFirst() + { + $source = $this->arrangeGridSourceDataNotLoadedWithoutRowsReturned(); + $columns = $this->arrangeGridWithColumnsIterator(); + + $page = 2; + $this->stubRequestWithData([Grid::REQUEST_QUERY_PAGE => $page]); + + $executeMap = [ + [$columns->getIterator(), $page, null, null, Column::DATA_CONJUNCTION, new Rows()], + [$columns->getIterator(), 0, null, null, Column::DATA_CONJUNCTION, new Rows()], + ]; + + $source + ->expects($this->exactly($page)) + ->method('execute') + ->will($this->returnValueMap($executeMap)); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]); + + $this->grid->handleRequest($this->request); + } + + public function testAddRowActionsToAllColumnsDuringHandleRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $actionsColumnId1 = 'actionsColumnId'; + $actionsColumn1 = $this + ->getMockBuilder(ActionsColumn::class) + ->disableOriginalConstructor() + ->getMock(); + $actionsColumn1 + ->method('getId') + ->willReturn($actionsColumnId1); + + $rowAction1 = new RowAction('title', 'route'); + $rowAction1->setColumn($actionsColumnId1); + + $this->grid->addRowAction($rowAction1); + + $rowAction2 = new RowAction('title', 'route'); + $rowAction2->setColumn($actionsColumnId1); + + $this->grid->addRowAction($rowAction2); + + $actionsColumnId2 = 'actionsColumnId2'; + $actionsColumn2 = $this + ->getMockBuilder(ActionsColumn::class) + ->disableOriginalConstructor() + ->getMock(); + $actionsColumn2 + ->method('getId') + ->willReturn($actionsColumnId2); + + $rowAction3 = new RowAction('title', 'route'); + $rowAction3->setColumn($actionsColumnId2); + + $this->grid->addRowAction($rowAction3); + + $hasColumnByIdMap = [ + [$actionsColumnId1, true, $actionsColumn1], + [$actionsColumnId2, true, $actionsColumn2], + ]; + + $columns = $this->arrangeGridWithColumnsIterator(); + $columns + ->method('hasColumnById') + ->will($this->returnValueMap($hasColumnByIdMap)); + + $this->grid->setColumns($columns); + + $actionsColumn1 + ->expects($this->once()) + ->method('setRowActions') + ->with([$rowAction1, $rowAction2]); + + $actionsColumn2 + ->expects($this->once()) + ->method('setRowActions') + ->with([$rowAction3]); + + $this->grid->handleRequest($this->request); + } + + public function testAddRowActionsToNotExistingColumnDuringHandleRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $actionsColumnId1 = 'actionsColumnId'; + + $rowAction1 = new RowAction('title', 'route'); + $rowAction1->setColumn($actionsColumnId1); + + $this->grid->addRowAction($rowAction1); + + $actionsColumnId2 = 'actionsColumnId2'; + + $rowAction2 = new RowAction('title', 'route'); + $rowAction2->setColumn($actionsColumnId2); + + $this->grid->addRowAction($rowAction2); + + $columns = $this->arrangeGridWithColumnsIterator(); + $this->grid->setColumns($columns); + $this->grid->setActionsColumnSize(2); + + $actionsColumnTitle = 'aTitle'; + $this->grid->setActionsColumnTitle($actionsColumnTitle); + + $missingActionsColumn1 = new ActionsColumn($actionsColumnId1, $actionsColumnTitle, [$rowAction1]); + $missingActionsColumn1->setSize(2); + $missingActionsColumn2 = new ActionsColumn($actionsColumnId2, $actionsColumnTitle, [$rowAction2]); + $missingActionsColumn2->setSize(2); + + $columns + ->expects($this->exactly(2)) + ->method('addColumn') + ->withConsecutive([$missingActionsColumn1], [$missingActionsColumn2]); + + $this->grid->handleRequest($this->request); + } + + public function testAddMassActionColumnsDuringHandleRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $columns = $this->arrangeGridWithColumnsIterator(); + + $this->grid->addMassAction(new MassAction('title')); + + $columns + ->expects($this->once()) + ->method('addColumn') + ->with($this->isInstanceOf(MassActionColumn::class), 1); + + $this->grid->handleRequest($this->request); + } + + public function testSetPrimaryFieldOnEachRow() + { + $row = $this->createMock(Row::class); + $row2 = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + $rows->addRow($row2); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridWithColumnsIterator(); + + $row + ->expects($this->once()) + ->method('setPrimaryField') + ->with('primaryID'); + + $row2 + ->expects($this->once()) + ->method('setPrimaryField') + ->with('primaryID'); + + $this->grid->handleRequest($this->request); + } + + public function testPopulateSelectFiltersInSourceFromDataIfDataLoadedDuringHandleRequest() + { + $columns = $this->arrangeGridWithColumnsIterator(); + + $source = $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $source + ->expects($this->once()) + ->method('populateSelectFiltersFromData') + ->with($columns); + + $this->grid->handleRequest($this->request); + } + + public function testPopulateSelectFiltersInSourceIfDataNotLoadedDuringHandleRequest() + { + $source = $this->arrangeGridSourceDataNotLoadedWithEmptyRows(); + + $columns = $this->arrangeGridWithColumnsIterator(); + + $source + ->expects($this->once()) + ->method('populateSelectFilters') + ->with($columns); + + $this->grid->handleRequest($this->request); + } + + public function testSetTotalCountFromDataDuringHandleRequest() + { + $totalCount = 2; + $this->arrangeGridSourceDataLoadedWithEmptyRows($totalCount); + $this->arrangeGridWithColumnsIterator(); + + $this->grid->handleRequest($this->request); + + $this->assertEquals($totalCount, $this->grid->getTotalCount()); + } + + public function testSetTotalCountDuringHandleRequest() + { + $totalCount = 2; + $this->arrangeGridSourceDataNotLoadedWithEmptyRows($totalCount); + $this->arrangeGridWithColumnsIterator(); + + $this->grid->handleRequest($this->request); + + $this->assertEquals($totalCount, $this->grid->getTotalCount()); + } + + public function testThrowsExceptionIfTotalCountNotIntegerFromDataDuringHandleRequest() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage(sprintf(Grid::INVALID_TOTAL_COUNT_EX_MSG, 'NULL')); + + $this->arrangeGridSourceDataLoadedWithEmptyRows(null); + $this->arrangeGridWithColumnsIterator(); + + $this->grid->handleRequest($this->request); + } + + public function testThrowsExceptionIfTotalCountNotIntegerDuringHandleRequest() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage(sprintf(Grid::INVALID_TOTAL_COUNT_EX_MSG, 'NULL')); + + $this->arrangeGridSourceDataNotLoadedWithEmptyRows(null); + $this->arrangeGridWithColumnsIterator(); + + $this->grid->handleRequest($this->request); + } + + public function testRaiseExceptionIfTweakDoesNotExistsDuringHandleRequest() + { + $tweakId = 'aValidTweakId'; + + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage(sprintf(Grid::TWEAK_NOT_DEFINED_EX_MSG, $tweakId)); + + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this->grid->handleRequest($this->request); + } + + public function testProcessTweakResetDuringHandleRequest() + { + $this->mockTweakReset(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessTweakFiltersDuringHandleRequest() + { + $this->mockTweakFilters(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessTweakOrderDuringHandleRequest() + { + $this->mockTweakOrder(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessTweakMassActionDuringHandleRequest() + { + $this->mockTweakMassAction(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessTweakPageDuringHandleRequest() + { + $this->mockTweakPage(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessTweakLimitDuringHandleRequest() + { + $this->mockTweakLimit(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessTweakExportDuringHandleRequest() + { + $this->mockTweakExport(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessTweakExportButNotFiltersPageOrderLimitDuringHandleRequest() + { + $this->mockTweakExportButNotFiltersPageOrderLimit(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessRemoveActiveTweakGroupsDuringHandleRequest() + { + $this->mockRemoveActiveTweakGroups(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessRemoveActiveTweakDuringHandleRequest() + { + $this->mockRemoveActiveTweak(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessAddActiveTweakDuringHandleRequest() + { + $this->mockAddActiveTweak(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessHiddenColumnsDuringHandleRequest() + { + $this->mockHiddenColumns(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessVisibleColumnsDuringHandleRequest() + { + $this->mockVisibleColumns(); + + $this->grid->handleRequest($this->request); + } + + public function testProcessColumnVisibilityDuringHandleRequest() + { + $this->mockColumnVisibility(); + + $this->grid->handleRequest($this->request); + } + + public function testGetTweaksWithUrlWithoutGetParameters() + { + $routeUrl = 'http://www.foo.com'; + + $title = 'aTweak'; + $tweak = ['filters' => [], 'order' => 'columnId', 'page' => 1, 'limit' => 50, 'export' => 1, 'massAction' => 1]; + $id = 'aValidTweakId'; + $group = 'tweakGroup'; + $tweakUrl = sprintf('%s?%s[%s]=%s', $routeUrl, $this->gridHash, Grid::REQUEST_QUERY_TWEAK, $id); + + $this->grid->addTweak($title, $tweak, $id, $group); + + $title2 = 'aTweak'; + $tweak2 = ['filters' => [], 'order' => 'columnId2', 'page' => 2, 'limit' => 100, 'export' => 0, 'massAction' => 0]; + $id2 = 'aValidTweakId2'; + $group2 = 'tweakGroup2'; + $tweakUrl2 = sprintf('%s?%s[%s]=%s', $routeUrl, $this->gridHash, Grid::REQUEST_QUERY_TWEAK, $id2); + + $this->grid->setRouteUrl($routeUrl); + + $this->grid->addTweak($title2, $tweak2, $id2, $group2); + + $result = [ + $id => array_merge(['title' => $title, 'id' => $id, 'group' => $group, 'url' => $tweakUrl], $tweak), + $id2 => array_merge(['title' => $title2, 'id' => $id2, 'group' => $group2, 'url' => $tweakUrl2], $tweak2), + ]; + + $this->assertEquals($result, $this->grid->getTweaks()); + } + + public function testGetTweaksWithUrlWithGetParameters() + { + $routeUrl = 'http://www.foo.com?foo=foo'; + + $title = 'aTweak'; + $tweak = ['filters' => [], 'order' => 'columnId', 'page' => 1, 'limit' => 50, 'export' => 1, 'massAction' => 1]; + $id = 'aValidTweakId'; + $group = 'tweakGroup'; + $tweakUrl = sprintf('%s&%s[%s]=%s', $routeUrl, $this->gridHash, Grid::REQUEST_QUERY_TWEAK, $id); + + $this->grid->addTweak($title, $tweak, $id, $group); + + $title2 = 'aTweak'; + $tweak2 = ['filters' => [], 'order' => 'columnId2', 'page' => 2, 'limit' => 100, 'export' => 0, 'massAction' => 0]; + $id2 = 'aValidTweakId2'; + $group2 = 'tweakGroup2'; + $tweakUrl2 = sprintf('%s&%s[%s]=%s', $routeUrl, $this->gridHash, Grid::REQUEST_QUERY_TWEAK, $id2); + + $this->grid->setRouteUrl($routeUrl); + + $this->grid->addTweak($title2, $tweak2, $id2, $group2); + + $result = [ + $id => array_merge(['title' => $title, 'id' => $id, 'group' => $group, 'url' => $tweakUrl], $tweak), + $id2 => array_merge(['title' => $title2, 'id' => $id2, 'group' => $group2, 'url' => $tweakUrl2], $tweak2), + ]; + + $this->assertEquals($result, $this->grid->getTweaks()); + } + + public function testRaiseExceptionIfGetNonExistentTweak() + { + $nonExistentTweak = 'aNonExistentTweak'; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf(Grid::NOT_VALID_TWEAK_ID_EX_MSG, $nonExistentTweak)); + + $tweakId = 'aValidTweakId'; + $tweak = ['filters' => [], 'order' => 'columnId', 'page' => 1, 'limit' => 50, 'export' => 1, 'massAction' => 1]; + + $routeUrl = 'http://www.foo.com'; + $this->grid->setRouteUrl($routeUrl); + + $this->grid->addTweak('title', $tweak, $tweakId, 'group'); + + $this->grid->getTweak($nonExistentTweak); + } + + public function testGetTweak() + { + $title = 'aTweak'; + $id = 'aValidTweakId'; + $group = 'tweakGroup'; + + $routeUrl = 'http://www.foo.com'; + $this->grid->setRouteUrl($routeUrl); + + $tweak = ['filters' => [], 'order' => 'columnId', 'page' => 1, 'limit' => 50, 'export' => 1, 'massAction' => 1]; + $tweakUrl = $routeUrl.sprintf('?%s[%s]=%s', $this->gridHash, Grid::REQUEST_QUERY_TWEAK, $id); + + $this->grid->addTweak($title, $tweak, $id, $group); + + $tweakResult = array_merge(['title' => $title, 'id' => $id, 'group' => $group, 'url' => $tweakUrl], $tweak); + + $this->assertEquals($tweakResult, $this->grid->getTweak($id)); + } + + public function testGetTweaksByGroupExcludingThoseWhoDoNotHaveTheGroup() + { + $routeUrl = 'http://www.foo.com'; + $this->grid->setRouteUrl($routeUrl); + + $title = 'aTweak'; + $id = 'aValidTweakId'; + $group = 'tweakGroup'; + $tweak = ['filters' => [], 'order' => 'columnId', 'page' => 1, 'limit' => 50, 'export' => 1, 'massAction' => 1]; + $tweakUrl = $routeUrl.sprintf('?%s[%s]=%s', $this->gridHash, Grid::REQUEST_QUERY_TWEAK, $id); + $tweakResult = [$id => array_merge(['title' => $title, 'id' => $id, 'group' => $group, 'url' => $tweakUrl], $tweak)]; + + $this->grid->addTweak($title, $tweak, $id, $group); + + $tweak2 = ['filters' => [], 'order' => 'columnId', 'page' => 2, 'limit' => 100, 'export' => 0, 'massAction' => 0]; + + $this->grid->addTweak('aTweak2', $tweak2, 'aValidTweakId2', 'tweakGroup2'); + + $this->assertEquals($tweakResult, $this->grid->getTweaksGroup($group)); + } + + public function testGetActiveTweaks() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $colFilter = ['from' => 'foo', 'to' => 'bar']; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('getFilterType') + ->willReturn('select'); + + $title = 'aTweak'; + $tweak = ['filters' => [$colId => $colFilter]]; + $tweakId = 'aValidTweakId'; + $tweakGroup = 'tweakGroup'; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this->grid->handleRequest($this->request); + + $this->assertEquals([$tweakGroup => $tweakId], $this->grid->getActiveTweaks()); + } + + public function testGetActiveTweakGroup() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $colFilter = ['from' => 'foo', 'to' => 'bar']; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('getFilterType') + ->willReturn('select'); + + $title = 'aTweak'; + $tweak = ['filters' => [$colId => $colFilter]]; + $tweakId = 'aValidTweakId'; + $tweakGroup = 'tweakGroup'; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this->grid->handleRequest($this->request); + + $this->assertEquals($tweakId, $this->grid->getActiveTweakGroup($tweakGroup)); + $this->assertEquals(-1, $this->grid->getActiveTweakGroup('invalidGroup')); + } + + public function testGetExportResponse() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_EXPORT => 0]); + + $response = $this + ->getMockBuilder(Response::class) + ->disableOriginalConstructor() + ->getMock(); + + $export = $this->createMock(ExportInterface::class); + $export + ->method('getResponse') + ->willReturn($response); + + $this->grid->addExport($export); + + $this->grid->handleRequest($this->request); + + $this->assertEquals($response, $this->grid->getExportResponse()); + } + + public function testIsReadyForExport() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_EXPORT => 0]); + + $response = $this + ->getMockBuilder(Response::class) + ->disableOriginalConstructor() + ->getMock(); + + $export = $this->createMock(ExportInterface::class); + $export + ->method('getResponse') + ->willReturn($response); + + $this->grid->addExport($export); + + $this->grid->handleRequest($this->request); + + $this->assertTrue($this->grid->isReadyForExport()); + } + + public function testSetPermanentFilters() + { + $filters = [ + 'colId1' => 'value', + 'colId2' => 'value', + ]; + + $this->grid->setPermanentFilters($filters); + + $this->assertEquals($filters, $this->grid->getPermanentFilters()); + } + + public function testSetDefaultFilters() + { + $filters = [ + 'colId1' => 'value', + 'colId2' => 'value', + ]; + + $this->grid->setDefaultFilters($filters); + + $this->assertEquals($filters, $this->grid->getDefaultFilters()); + } + + public function testSetDefaultOrder() + { + $colId = 'COLID'; + $order = 'ASC'; + + $this->grid->setDefaultOrder($colId, $order); + + $this->assertEquals(sprintf("$colId|%s", strtolower($order)), $this->grid->getDefaultOrder()); + } + + public function testGetRows() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $this->grid->handleRequest($this->request); + + $this->assertEquals($rows, $this->grid->getRows()); + } + + public function testGetTotalCount() + { + $totalCount = 20; + $this->arrangeGridSourceDataLoadedWithEmptyRows($totalCount); + $this->arrangeGridWithColumnsIterator(); + + $this->grid->handleRequest($this->request); + + $this->assertEquals($totalCount, $this->grid->getTotalCount()); + } + + public function testGetPageCountWithoutLimit() + { + $this->assertEquals(1, $this->grid->getPageCount()); + } + + public function testGetPageCount() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(29); + $this->arrangeGridWithColumnsIterator(); + + $limit = 10; + $this->stubRequestWithData([Grid::REQUEST_QUERY_LIMIT => $limit]); + + $this->grid->setLimits($limit); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_LIMIT => $limit, Grid::REQUEST_QUERY_PAGE => 0]); + + $this->grid->handleRequest($this->request); + + $this->assertEquals(3, $this->grid->getPageCount()); + } + + public function testIsPagerSectionNotVisibleWhenNoLimitsSetted() + { + $this->assertFalse($this->grid->isPagerSectionVisible()); + } + + public function testIsPagerSectionNotVisibleWhenSmallestLimitGreaterThanTotalCount() + { + $this->grid->setLimits([10, 20, 30]); + + $this->assertFalse($this->grid->isPagerSectionVisible()); + } + + public function testIsPagerSectionVisibleWhenSmallestLimitLowestThanTotalCount() + { + $this->grid->setLimits([10, 20, 30]); + + $this->assertFalse($this->grid->isPagerSectionVisible()); + } + + public function testDeleteAction() + { + $source = $this->createMock(Source::class); + + $this->grid->setSource($source); + + $deleteIds = [1, 2, 3]; + $source + ->expects($this->once()) + ->method('delete') + ->with($deleteIds); + + $this->grid->deleteAction($deleteIds); + } + + public function testGetRawDataWithAllColumnsIfNoColumnsRequested() + { + $rows = new Rows(); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column1 = $this->arrangeGridPrimaryColumn(); + $col1Id = 'col1Id'; + $column1 + ->method('getId') + ->willReturn($col1Id); + + $col2Id = 'col2Id'; + $column2 = $this->stubColumn($col2Id); + $this->grid->addColumn($column2); + + $rowCol1Field = 'rowCol1Field'; + $rowCol2Field = 'rowCol2Field'; + + $getFieldRowMap = [ + [$col1Id, $rowCol1Field], + [$col2Id, $rowCol2Field], + ]; + + $row = $this->createMock(Row::class); + $row + ->method('getField') + ->will($this->returnValueMap($getFieldRowMap)); + + $rows->addRow($row); + + $row2Col1Field = 'row2Col1Field'; + $row2Col2Field = 'row2Col2Field'; + + $getFieldRow2Map = [ + [$col1Id, $row2Col1Field], + [$col2Id, $row2Col2Field], + ]; + + $row2 = $this->createMock(Row::class); + $row2 + ->method('getField') + ->will($this->returnValueMap($getFieldRow2Map)); + + $rows->addRow($row2); + + $this->grid->handleRequest($this->request); + + $this->assertEquals( + [ + [$col1Id => $rowCol1Field, $col2Id => $rowCol2Field], + [$col1Id => $row2Col1Field, $col2Id => $row2Col2Field], + ], + $this->grid->getRawData() + ); + } + + public function testGetRawDataWithSubsetOfColumns() + { + $rows = new Rows(); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column1 = $this->arrangeGridPrimaryColumn(); + $col1Id = 'col1Id'; + $column1 + ->method('getId') + ->willReturn($col1Id); + + $col2Id = 'col2Id'; + $column2 = $this->stubColumn($col2Id); + $this->grid->addColumn($column2); + + $rowCol1Field = 'rowCol1Field'; + $rowCol2Field = 'rowCol2Field'; + + $getFieldRowMap = [ + [$col1Id, $rowCol1Field], + [$col2Id, $rowCol2Field], + ]; + + $row = $this->createMock(Row::class); + $row + ->method('getField') + ->will($this->returnValueMap($getFieldRowMap)); + + $rows->addRow($row); + + $row2Col1Field = 'row2Col1Field'; + $row2Col2Field = 'row2Col2Field'; + + $getFieldRow2Map = [ + [$col1Id, $row2Col1Field], + [$col2Id, $row2Col2Field], + ]; + + $row2 = $this->createMock(Row::class); + $row2 + ->method('getField') + ->will($this->returnValueMap($getFieldRow2Map)); + + $rows->addRow($row2); + + $this->grid->handleRequest($this->request); + + $this->assertEquals( + [ + [$col1Id => $rowCol1Field], + [$col1Id => $row2Col1Field], + ], + $this->grid->getRawData($col1Id) + ); + } + + public function testGetRawDataWithoutNamedIndexesResult() + { + $rows = new Rows(); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + $colId = 'colId'; + $column + ->method('getId') + ->willReturn($colId); + + $rowColField = 'rowColField'; + $row = $this->createMock(Row::class); + $row + ->method('getField') + ->with($colId) + ->willReturn($rowColField); + + $rows->addRow($row); + + $row2ColField = 'row2ColField'; + $row2 = $this->createMock(Row::class); + $row2 + ->method('getField') + ->with($colId) + ->willReturn($row2ColField); + + $rows->addRow($row2); + + $this->grid->handleRequest($this->request); + + $this->assertEquals( + [ + [$rowColField], + [$row2ColField], + ], + $this->grid->getRawData($colId, false) + ); + } + + // public function testGetFiltersRaiseExceptionIfNoRequestProcessed() + // { + // $this->expectException(\Exception::class); + // $this->expectExceptionMessage(Grid::GET_FILTERS_NO_REQUEST_HANDLED_EX_MSG); + // + // $this->grid->getFilters(); + // } + + public function testGetFilters() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $col1Id = 'col1Id'; + $column1 = $this->stubColumn($col1Id); + $this->grid->addColumn($column1); + + $col2Id = 'col2Id'; + $column2 = $this->stubColumnWithDefaultOperator(Column::OPERATOR_GT, $col2Id); + $this->grid->addColumn($column2); + + $this->stubRequestWithData([ + Grid::REQUEST_QUERY_MASS_ACTION_ALL_KEYS_SELECTED => true, + Grid::REQUEST_QUERY_MASS_ACTION => true, + Grid::REQUEST_QUERY_EXPORT => false, + Grid::REQUEST_QUERY_PAGE => 1, + Grid::REQUEST_QUERY_LIMIT => 10, + Grid::REQUEST_QUERY_ORDER => null, + Grid::REQUEST_QUERY_TEMPLATE => 'aTemplate', + Grid::REQUEST_QUERY_RESET => false, + MassActionColumn::ID => 'massActionColId', + ]); + + $filter1Operator = Column::OPERATOR_BTW; + $filter1From = 'from1'; + $filter1To = 'to1'; + $filter1 = new Filter($filter1Operator, ['from' => $filter1From, 'to' => $filter1To]); + + $filter2Operator = Column::OPERATOR_GT; + $filter2From = 'from2'; + $filter2 = new Filter($filter2Operator, $filter2From); + + $this->grid->setDefaultFilters([ + $col1Id => ['operator' => $filter1Operator, 'from' => $filter1From, 'to' => $filter1To], + $col2Id => ['from' => $filter2From], + ]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->withConsecutive( + [$this->gridHash, [Grid::REQUEST_QUERY_PAGE => 0]], + [$this->gridHash, [ + Grid::REQUEST_QUERY_PAGE => 0, + $col1Id => ['operator' => $filter1Operator, 'from' => $filter1From, 'to' => $filter1To], + $col2Id => ['from' => $filter2From], ], + ] + ); + + $this->grid->handleRequest($this->request); + + $this->assertEquals( + [$col1Id => $filter1, $col2Id => $filter2], + $this->grid->getFilters() + ); + } + + // public function testGetFilterRaiseExceptionIfNoRequestProcessed() + // { + // $this->expectException(\Exception::class); + // $this->expectExceptionMessage(Grid::GET_FILTERS_NO_REQUEST_HANDLED_EX_MSG); + // + // $this->grid->getFilter('foo'); + // } + + public function testGetFilterReturnNullIfRequestedColumnHasNoFilter() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->grid->handleRequest($this->request); + + $this->assertNull($this->grid->getFilter('foo')); + } + + public function testGetFilter() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $colId = 'col1Id'; + $column = $this->stubColumn($colId); + $this->grid->addColumn($column); + + $filterOperator = Column::OPERATOR_BTW; + $filterFrom = 'from1'; + $filterTo = 'to1'; + $filter = new Filter($filterOperator, ['from' => $filterFrom, 'to' => $filterTo]); + + $this->grid->setDefaultFilters([ + $colId => ['operator' => $filterOperator, 'from' => $filterFrom, 'to' => $filterTo], + ]); + + $this->grid->handleRequest($this->request); + + $this->assertEquals($filter, $this->grid->getFilter($colId)); + } + + // public function testHasFilterRaiseExceptionIfNoRequestProcessed() + // { + // $this->expectException(\Exception::class); + // $this->expectExceptionMessage(Grid::HAS_FILTER_NO_REQUEST_HANDLED_EX_MSG); + // + // $this->grid->hasFilter('foo'); + // } + + public function testHasFilterReturnNullIfRequestedColumnHasNoFilter() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->grid->handleRequest($this->request); + + $this->assertFalse($this->grid->hasFilter('foo')); + } + + public function testHasFilter() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $colId = 'col1Id'; + $column = $this->stubColumn($colId); + $this->grid->addColumn($column); + + $filterOperator = Column::OPERATOR_BTW; + $filterFrom = 'from1'; + $filterTo = 'to1'; + + $this->grid->setDefaultFilters([ + $colId => ['operator' => $filterOperator, 'from' => $filterFrom, 'to' => $filterTo], + ]); + + $this->grid->handleRequest($this->request); + + $this->assertTrue($this->grid->hasFilter($colId)); + } + + public function testRaiseExceptionIfNoSourceSettedDuringRedirect() + { + $this->expectException(\Exception::class); + $this->expectExceptionMessage(Grid::SOURCE_NOT_SETTED_EX_MSG); + + $this->grid->isReadyForRedirect(); + } + + public function testCreateHashWithIdDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->grid->isReadyForRedirect(); + + $this->assertEquals($this->gridHash, $this->grid->getHash()); + } + + public function testCreateHashWithMd5DuringRedirect() + { + $this->arrange($this->createMock(GridConfigInterface::class), null); + + $sourceHash = '4f403d7e887f7d443360504a01aaa30e'; + + $this->arrangeGridSourceDataLoadedWithEmptyRows(0, $sourceHash); + + $column = $this->stubPrimaryColumn(); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column); + $this->grid->setColumns($columns); + + $controller = 'aController'; + + $this + ->request + ->expects($this->at(0)) + ->method('get') + ->with('_controller') + ->willReturn($controller); + + $this->grid->isReadyForRedirect(); + + $this->assertEquals('grid_' . md5($controller . $columns->getHash() . $sourceHash), $this->grid->getHash()); + } + + public function testResetGridSessionWhenResetFiltersIsPressedDuringRedirect() + { + $this->mockResetGridSessionWhenResetFilterIsPressed(); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testNotResetGridSessionWhenXmlHttpRequestDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this + ->request + ->method('isXmlHttpRequest') + ->willReturn(true); + + $this + ->session + ->expects($this->never()) + ->method('remove') + ->with($this->gridHash); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testNotResetGridSessionWhenPersistenceSettedDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this + ->request + ->method('isXmlHttpRequest') + ->willReturn(true); + + $this + ->session + ->expects($this->never()) + ->method('remove') + ->with($this->gridHash); + + $this->grid->setPersistence(true); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testNotResetGridSessionWhenRefererIsSameGridDuringRedirect() + { + $this->mockNotResetGridSessionWhenSameGridReferer(); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testStartNewSessionDuringRedirectOnFirstRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->grid->isReadyForRedirect(); + + $this->assertEquals(true, $this->grid->getNewSession()); + } + + public function testStartKeepSessionDuringRedirectNotOnFirstRequest() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this + ->session + ->method('get') + ->with($this->gridHash) + ->willReturn('sessionData'); + + $this->grid->isReadyForRedirect(); + + $this->assertEquals(false, $this->grid->getNewSession()); + } + + public function testProcessHiddenColumnsDuringRedirect() + { + $this->mockHiddenColumns(); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testProcessVisibleColumnsDuringRedirect() + { + $this->mockVisibleColumns(); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testProcessColumnVisibilityDuringRedirect() + { + $this->mockColumnVisibility(); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testRaiseExceptionIfMassActionIdNotValidDuringRedirect() + { + $massActionId = 10; + + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage(sprintf(Grid::MASS_ACTION_NOT_DEFINED_EX_MSG, $massActionId)); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_MASS_ACTION => $massActionId]); + + $this->grid->isReadyForRedirect(); + } + + public function testRaiseExceptionIfMassActionCallbackNotValidDuringRedirect() + { + $invalidCallback = 'invalidCallback'; + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage(sprintf(Grid::MASS_ACTION_CALLBACK_NOT_VALID_EX_MSG, $invalidCallback)); + + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_MASS_ACTION => 0]); + + $massAction = $this->stubMassActionWithCallback($invalidCallback); + + $this->grid->addMassAction($massAction); + + $this->grid->isReadyForRedirect(); + } + + public function testResetPageAndLimitIfMassActionHandleAllDataDuringRedirect() + { + $this->mockResetPageAndLimitIfMassActionAndAllKeys(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + + $this->assertEquals(0, $this->grid->getLimit()); + } + + public function testMassActionResponseFromCallbackDuringRedirect() + { + $callbackResponse = $this->mockMassActionCallbackResponse(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + + $this->assertEquals($callbackResponse, $this->grid->getMassActionResponse()); + } + + public function testMassActionResponseFromControllerActionDuringRedirect() + { + $callbackResponse = $this->mockMassActionControllerResponse(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + + $this->assertEquals($callbackResponse, $this->grid->getMassActionResponse()); + } + + public function testRaiseExceptionIfExportIdNotValidDuringRedirect() + { + $exportId = 10; + + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage(sprintf(Grid::EXPORT_NOT_DEFINED_EX_MSG, $exportId)); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_EXPORT => $exportId]); + + $this->grid->isReadyForRedirect(); + } + + public function testProcessExportsDuringRedirect() + { + $response = $this->mockExports(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + + $this->assertEquals(0, $this->grid->getPage()); + $this->assertEquals(0, $this->grid->getLimit()); + $this->assertEquals(true, $this->grid->isReadyForExport()); + $this->assertEquals($response, $this->grid->getExportResponse()); + } + + public function testProcessExportsButNotFiltersPageOrderLimitDuringRedirect() + { + $this->mockExportsButNotFiltersPageOrderLimit(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testRaiseExceptionIfTweakDoesNotExistsDuringRedirect() + { + $tweakId = 'aValidTweakId'; + + $this->expectException(\OutOfBoundsException::class); + $this->expectExceptionMessage(sprintf(Grid::TWEAK_NOT_DEFINED_EX_MSG, $tweakId)); + + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessTweakResetDuringRedirect() + { + $this->mockTweakReset(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessTweakFiltersDuringRedirect() + { + $this->mockTweakFilters(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessTweakOrderDuringRedirect() + { + $this->mockTweakOrder(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessTweakMassActionDuringRedirect() + { + $this->mockTweakMassAction(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessTweakPageDuringRedirect() + { + $this->mockTweakPage(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessTweakLimitDuringRedirect() + { + $this->mockTweakLimit(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessTweakExportDuringRedirect() + { + $this->mockTweakExport(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessTweakExportButNotFiltersPageOrderLimitDuringRedirect() + { + $this->mockTweakExportButNotFiltersPageOrderLimit(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessRemoveActiveTweakGroupsDuringRedirect() + { + $this->mockRemoveActiveTweakGroups(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessRemoveActiveTweakDuringRedirect() + { + $this->mockRemoveActiveTweak(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessAddActiveTweakDuringRedirect() + { + $this->mockAddActiveTweak(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessPageDuringRedirect() + { + $this->mockPageRequestData(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessPageWithQueryOrderingDuringRedirect() + { + $this->mockPageQueryOrderRequestData(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessPageWithQueryLimitDuringRedirect() + { + $this->mockPageLimitRequestData(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessPageWithMassActionDuringRedirect() + { + $this->mockPageMassActionRequestData(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessPageWithFiltersAndRequestDataDuringRedirect() + { + $this->mockPageFiltersRequestData(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessPageWithFiltersDifferentFromSelectDuringRedirect() + { + $this->mockPageNotSelectFilterRequestData(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessPageWithSelectFilterColumnNotSelectMultiDuringRedirect() + { + $this->mockPageColumnNotSelectMultiRequestData(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessOrderDescDuringRedirect() + { + $colId = 'colId'; + $order = 'desc'; + $queryOrder = "$colId|$order"; + + $column = $this->mockOrderRequestData($colId, $order); + + $column + ->expects($this->never()) + ->method('setOrder') + ->with($order); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_ORDER => $queryOrder, Grid::REQUEST_QUERY_PAGE => 0]); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessOrderAscDuringRedirect() + { + $colId = 'colId'; + $order = 'asc'; + $queryOrder = "$colId|$order"; + + $column = $this->mockOrderRequestData($colId, $order); + + $column + ->expects($this->never()) + ->method('setOrder') + ->with($order); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_ORDER => $queryOrder, Grid::REQUEST_QUERY_PAGE => 0]); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessOrderColumnNotSortableDuringRedirect() + { + $this->mockOrderColumnNotSortable(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testColumnsNotOrderedIfNoOrderRequestedDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->stubPrimaryColumn(); + $column + ->method('isSortable') + ->willReturn(true); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column); + $this->grid->setColumns($columns); + + $this->stubRequestWithData([]); + + $column + ->expects($this->never()) + ->method('setOrder'); + + $this->assertFalse($this->grid->isReadyForRedirect()); + + $this->assertEquals(0, $this->grid->getPage()); + } + + public function testProcessConfiguredLimitDuringRedirect() + { + $this->mockConfiguredLimitRequestData(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessNonConfiguredLimitDuringRedirect() + { + $this->mockNonConfiguredLimitRequestData(); + + $this->assertTrue($this->grid->isReadyForRedirect()); + + $this->assertEmpty($this->grid->getLimit()); + } + + public function testSetDefaultSessionFiltersIfNotRequestDataDuringRedirect() + { + $this->mockDefaultSessionFiltersWithoutRequestData(); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testSetDefaultSessionFiltersIfSessionDataXmlHttpRequestAndNotExportDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $col1Id = 'col1'; + $col2Id = 'col2'; + $col3Id = 'col3'; + $col4Id = 'col4'; + $col5Id = 'col5'; + + $col1FilterValue = 'val1'; + $col2FilterValue = ['val2']; + + $col5From = 'foo'; + $col5To = 'bar'; + + [$column1, $column2, $column3, $column4, $column5] = $this->arrangeColumnsFilters( + $col1Id, + $col2Id, + $col3Id, + $col4Id, + $col5Id, + $col1FilterValue, + $col2FilterValue, + $col5From, + $col5To + ); + + $page = 1; + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_PAGE => $page]); + $this + ->request + ->method('isXmlHttpRequest') + ->willReturn(true); + + $column + ->expects($this->never()) + ->method('setData') + ->with($this->anything()); + $column1 + ->expects($this->once()) + ->method('setData') + ->with(['from' => $col1FilterValue]); + $column2 + ->expects($this->once()) + ->method('setData') + ->with(['from' => $col2FilterValue]); + $column3 + ->expects($this->once()) + ->method('setData') + ->with(['from' => 1]); + $column4 + ->expects($this->once()) + ->method('setData') + ->with(['from' => 0]); + $column5 + ->expects($this->once()) + ->method('setData') + ->with(['from' => [$col5From], 'to' => [$col5To]]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->withConsecutive( + [$this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]], + [$this->gridHash, [ + $col1Id => ['from' => $col1FilterValue], + $col2Id => ['from' => $col2FilterValue], + $col3Id => ['from' => 1], + $col4Id => ['from' => 0], + $col5Id => ['from' => [$col5From], 'to' => [$col5To]], + Grid::REQUEST_QUERY_PAGE => $page, ], + ] + ); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testNotSetDefaultSessionFiltersIfHasRequestDataNotXmlHttpButExportDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $col1Id = 'col1'; + $col2Id = 'col2'; + $col3Id = 'col3'; + $col4Id = 'col4'; + $col5Id = 'col5'; + + $col1FilterValue = 'val1'; + $col2FilterValue = ['val2']; + + $col5From = 'foo'; + $col5To = 'bar'; + + [$column1, $column2, $column3, $column4, $column5] = $this->arrangeColumnsFilters( + $col1Id, + $col2Id, + $col3Id, + $col4Id, + $col5Id, + $col1FilterValue, + $col2FilterValue, + $col5From, + $col5To + ); + + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_EXPORT => 0]); + + $this->grid->addExport($this->createMock(Export::class)); + + $column + ->expects($this->never()) + ->method('setData') + ->with($this->anything()); + $column1 + ->expects($this->never()) + ->method('setData') + ->with(['from' => $col1FilterValue]); + $column2 + ->expects($this->never()) + ->method('setData') + ->with(['from' => $col2FilterValue]); + $column3 + ->expects($this->never()) + ->method('setData') + ->with(['from' => 1]); + $column4 + ->expects($this->never()) + ->method('setData') + ->with(['from' => 0]); + $column5 + ->expects($this->never()) + ->method('setData') + ->with(['from' => [$col5From], 'to' => [$col5To]]); + + $this + ->session + ->expects($this->never()) + ->method('set'); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testNotSetDefaultSessionFiltersIfHasRequestDataNotXmlHttpAndNotExportDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $col1Id = 'col1'; + $col2Id = 'col2'; + $col3Id = 'col3'; + $col4Id = 'col4'; + $col5Id = 'col5'; + + $col1FilterValue = 'val1'; + $col2FilterValue = ['val2']; + + $col5From = 'foo'; + $col5To = 'bar'; + + [$column1, $column2, $column3, $column4, $column5] = $this->arrangeColumnsFilters( + $col1Id, + $col2Id, + $col3Id, + $col4Id, + $col5Id, + $col1FilterValue, + $col2FilterValue, + $col5From, + $col5To + ); + + $page = 0; + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_PAGE => $page]); + + $column + ->expects($this->never()) + ->method('setData') + ->with($this->anything()); + $column1 + ->expects($this->never()) + ->method('setData') + ->with(['from' => $col1FilterValue]); + $column2 + ->expects($this->never()) + ->method('setData') + ->with(['from' => $col2FilterValue]); + $column3 + ->expects($this->never()) + ->method('setData') + ->with(['from' => 1]); + $column4 + ->expects($this->never()) + ->method('setData') + ->with(['from' => 0]); + $column5 + ->expects($this->never()) + ->method('setData') + ->with(['from' => [$col5From], 'to' => [$col5To]]); + + $this + ->session + ->expects($this->once()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testSetDefaultPageRaiseExceptionIfPageHasNegativeValueDuringRedirect() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(Grid::PAGE_NOT_VALID_EX_MSG); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->grid->setDefaultPage(-1); + + $this->grid->isReadyForRedirect(); + } + + public function testSetDefaultPageIfNotRequestDataDuringRedirect() + { + $this->mockDefaultPage(); + + $this->grid->isReadyForRedirect(); + } + + public function testSetDefaultPageIfRequestDataXmlHttpRequestAndNotExportDuringRedirect() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $this->grid->setDefaultPage(2); + + $page = 1; + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_PAGE => $page]); + $this + ->request + ->method('isXmlHttpRequest') + ->willReturn(true); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => 1]); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testNotSetDefaultPageIfHasRequestDataNotXmlHttpButExportDuringRedirect() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $this->grid->setDefaultPage(2); + + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_EXPORT => 0]); + + $this->grid->addExport($this->createMock(Export::class)); + + $this + ->session + ->expects($this->never()) + ->method('set'); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testNotSetDefaultPageIfHasRequestDataNotXmlHttpAndNotExportDuringRedirect() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $this->grid->setDefaultPage(2); + + $page = 1; + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_PAGE => $page]); + + $this + ->session + ->expects($this->once()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testSetDefaultOrderRaiseExceptionIfOrderNotAscNeitherDescDuringRedirect() + { + $columnOrder = 'foo'; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf(Grid::COLUMN_ORDER_NOT_VALID_EX_MSG, $columnOrder)); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $colId = 'col'; + $column = $this->stubColumn($colId); + $this->grid->addColumn($column); + + $this->grid->setDefaultOrder($colId, $columnOrder); + + $this->grid->isReadyForRedirect(); + } + public function testSetDefaultOrderRaiseExceptionIfColumnDoesNotExistsDuringRedirect() + { + $colId = 'col'; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf(Columns::MISSING_COLUMN_EX_MSG, $colId)); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->arrangeGridPrimaryColumn(); + + $this->grid->setDefaultOrder($colId, 'asc'); + + $this->grid->isReadyForRedirect(); + } + + public function testSetDefaultOrderAscIfNotRequestDataDuringRedirect() + { + $this->mockDefaultOrder('asc'); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testSetDefaultOrderDescIfNotRequestDataDuringRedirect() + { + $this->mockDefaultOrder('desc'); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testSetDefaultOrderIfRequestDataXmlHttpRequestAndNotExportDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $columnId = 'columnId'; + $order = 'desc'; + $column + ->method('getId') + ->willReturn($columnId); + + $this->grid->setDefaultOrder($columnId, $order); + + $page = 1; + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_PAGE => $page]); + $this + ->request + ->method('isXmlHttpRequest') + ->willReturn(true); + + $column + ->expects($this->once()) + ->method('setOrder') + ->with($order); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->withConsecutive( + [$this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]], + [$this->gridHash, [Grid::REQUEST_QUERY_ORDER => "$columnId|$order", Grid::REQUEST_QUERY_PAGE => $page]] + ); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testNotSetDefaultOrderIfHasRequestDataNotXmlHttpButExportDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $columnId = 'columnId'; + $order = 'desc'; + $column + ->method('getId') + ->willReturn($columnId); + + $this->grid->setDefaultOrder($columnId, $order); + + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_EXPORT => 0]); + + $this->grid->addExport($this->createMock(Export::class)); + + $column + ->expects($this->never()) + ->method('setOrder') + ->with($order); + + $this + ->session + ->expects($this->never()) + ->method('set'); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testNotSetDefaultOrderIfHasRequestDataNotXmlHttpAndNotExportDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $columnId = 'columnId'; + $order = 'desc'; + $column + ->method('getId') + ->willReturn($columnId); + + $this->grid->setDefaultOrder($columnId, $order); + + $page = 1; + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_PAGE => $page]); + + $column + ->expects($this->never()) + ->method('setOrder') + ->with($order); + + $this + ->session + ->expects($this->once()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testSetDefaultLimitRaiseExceptionIfLimitIsNotAPositiveDuringRedirect() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(Grid::DEFAULT_LIMIT_NOT_VALID_EX_MSG); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->grid->setDefaultLimit(-1); + + $this->grid->isReadyForRedirect(); + } + + public function testSetDefaultLimitRaiseExceptionIfLimitIsNotDefinedInGridLimitsDuringRedirect() + { + $limit = 2; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf(Grid::LIMIT_NOT_DEFINED_EX_MSG, $limit)); + + $source = $this->createMock(Source::class); + $this->grid->setSource($source); + + $this->grid->setDefaultLimit($limit); + + $this->grid->isReadyForRedirect(); + } + + public function testSetDefaultLimitIfNotSessionDataDuringHandleRedirect() + { + $this->mockDefaultLimit(); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testSetDefaultLimitIfRequestDataXmlHttpRequestAndNotExportDuringHandleRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $limit = 2; + $this->grid->setLimits([$limit => "$limit"]); + $this->grid->setDefaultLimit($limit); + + $page = 1; + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_PAGE => $page]); + $this + ->request + ->method('isXmlHttpRequest') + ->willReturn(true); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->withConsecutive( + [$this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]], + [$this->gridHash, [Grid::REQUEST_QUERY_LIMIT => $limit, Grid::REQUEST_QUERY_PAGE => $page]] + ); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testNotSetDefaultLimitIfHasRequestDataNotXmlHttpButExportDuringHandleRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $limit = 2; + $this->grid->setLimits([$limit => "$limit"]); + $this->grid->setDefaultLimit($limit); + + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_EXPORT => 0]); + + $this->grid->addExport($this->createMock(Export::class)); + + $this + ->session + ->expects($this->never()) + ->method('set'); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testNotSetDefaultLimitIfHasRequestDataNotXmlHttpAndNotExportDuringHandleRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $limit = 2; + $this->grid->setLimits([$limit => "$limit"]); + $this->grid->setDefaultLimit($limit); + + $page = 1; + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_PAGE => $page]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testProcessDefaultTweaksIfNotRequestDataDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + [$group, $tweakId] = $this->arrangeDefaultTweaks(1); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, ['tweaks' => [$group => $tweakId], Grid::REQUEST_QUERY_PAGE => 1]); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testProcessDefaultTweaksIfRequestDataXmlHttpRequestAndNotExportDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $tweakPage = 1; + [$group, $tweakId] = $this->arrangeDefaultTweaks($tweakPage); + + $requestPage = 2; + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_PAGE => $requestPage]); + $this + ->request + ->method('isXmlHttpRequest') + ->willReturn(true); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->withConsecutive( + [$this->gridHash, [Grid::REQUEST_QUERY_PAGE => $requestPage]], + [$this->gridHash, ['tweaks' => [$group => $tweakId], Grid::REQUEST_QUERY_PAGE => $tweakPage]] + ); + + $this->assertFalse($this->grid->isReadyForRedirect()); + } + + public function testNotProcessDefaultTweaksIfHasRequestDataNotXmlHttpButExportDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->arrangeDefaultTweaks(1); + + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_EXPORT => 0]); + + $this->grid->addExport($this->createMock(Export::class)); + + $this + ->session + ->expects($this->never()) + ->method('set'); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testNotProcessDefaultTweaksIfHasRequestDataNotXmlHttpAndNotExportDuringRedirect() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->arrangeDefaultTweaks(1); + + $requestPage = 2; + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_PAGE => $requestPage]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => $requestPage]); + + $this->assertTrue($this->grid->isReadyForRedirect()); + } + + public function testGetGridRedirectResponse() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $this + ->request + ->method('get') + ->willReturn([Grid::REQUEST_QUERY_PAGE => 10]); + + $this->grid->setRouteUrl('aRouteUrl'); + + $this->assertInstanceOf(RedirectResponse::class, $this->grid->getGridResponse()); + } + + public function testGetGridExportResponse() + { + $exportResponse = $this->mockExports(); + + $this->assertEquals($exportResponse, $this->grid->getGridResponse()); + } + + public function testGetGridMassActionCallbackRedirectResponse() + { + $response = $this->mockMassActionCallbackResponse(); + + $this->assertEquals($response, $this->grid->getGridResponse()); + } + + public function testGetGridMassActionControllerResponse() + { + $response = $this->mockMassActionControllerResponse(); + + $this->assertEquals($response, $this->grid->getGridResponse()); + } + + public function testGetGridWithoutParams() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->assertEquals(['grid' => $this->grid], $this->grid->getGridResponse()); + } + + public function testGetGridWithoutView() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $param1 = 'foo'; + $param2 = 'bar'; + $params = [$param1, $param2]; + $this->assertEquals(['grid' => $this->grid, $param1, $param2], $this->grid->getGridResponse($params)); + } + + /* + public function testGetGridWithViewWithoutParams() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $view = 'aView'; + + $content = "test123"; + + $this->twig + ->method('render') + ->with($view, ['grid' => $this->grid]) + ->willReturn($content); + + $this->assertEquals($content, $this->grid->getGridResponse($view)->getContent()); + } + + public function testGetGridWithViewWithViewAndParams() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $view = 'aView'; + + $param1 = 'foo'; + $param2 = 'bar'; + $params = [$param1, $param2]; + + $content = "test123"; + + $this->twig + ->method('render') + ->with($view, ['grid' => $this->grid, $param1, $param2]) + ->willReturn($content); + $this->assertEquals($content, $this->grid->getGridResponse($view, $params)->getContent()); + } + */ + public function setUp(): void + { + $this->arrange($this->createMock(GridConfigInterface::class)); + } + + /** + * @param $gridConfigInterface + * @param string $id + * @param \PHPUnit_Framework_MockObject_MockObject $httpKernel + */ + private function arrange($gridConfigInterface = null, $id = 'id', $httpKernel = null) + { + $session = $this + ->getMockBuilder(Session::class) + ->disableOriginalConstructor() + ->getMock(); + $this->session = $session; + + $request = $this + ->getMockBuilder(Request::class) + ->disableOriginalConstructor() + ->getMock(); + $request + ->method('getSession') + ->willReturn($session); + $request->headers = $this + ->getMockBuilder(HeaderBag::class) + ->disableOriginalConstructor() + ->getMock(); + $this->request = $request; + + $request->attributes = new ParameterBag([]); + + $requestStack = $this->createMock(RequestStack::class); + $requestStack + ->method('getCurrentRequest') + ->willReturn($request); + $this->requestStack = $requestStack; + + $this->router = $this + ->getMockBuilder(Router::class) + ->disableOriginalConstructor() + ->getMock(); + + $authChecker = $this->createMock(AuthorizationCheckerInterface::class); + $this->authChecker = $authChecker; + + $this->twig = $this->createMock(Environment::class); + + $containerGetMap = [ + ['router', Container::EXCEPTION_ON_INVALID_REFERENCE, $this->router], + ['request_stack', Container::EXCEPTION_ON_INVALID_REFERENCE, $this->requestStack], + ['security.authorization_checker', Container::EXCEPTION_ON_INVALID_REFERENCE, $this->authChecker], + ['http_kernel', Container::EXCEPTION_ON_INVALID_REFERENCE, $httpKernel], + ['twig', Container::EXCEPTION_ON_INVALID_REFERENCE, $this->twig], + ]; + + $container = $this + ->getMockBuilder(Container::class) + ->disableOriginalConstructor() + ->getMock(); + $container + ->method('get') + ->will($this->returnValueMap($containerGetMap)); + $this->container = $container; + + $this->gridId = (string) $id; + $this->gridHash = 'grid_' . $this->gridId; + + $this->grid = new Grid($container, $this->authChecker, $this->twig, $this->gridId, $gridConfigInterface); + } + + private function mockResetGridSessionWhenResetFilterIsPressed() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_RESET => true]); + + $this + ->request + ->method('isXmlHttpRequest') + ->willReturn(true); + $this + ->request + ->headers + ->method('get') + ->with('referer') + ->willReturn('aReferer'); + + $this + ->session + ->expects($this->once()) + ->method('remove') + ->with($this->gridHash); + + $this->grid->setPersistence(true); + } + + private function mockNotResetGridSessionWhenSameGridReferer() + { + $scheme = 'http'; + $host = 'www.foo.com/'; + $basUrl = 'baseurl'; + $pathInfo = '/info'; + + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this + ->request + ->method('isXmlHttpRequest') + ->willReturn(true); + $this + ->request + ->method('getScheme') + ->willReturn($scheme); + $this + ->request + ->method('getHttpHost') + ->willReturn($host); + $this + ->request + ->method('getBaseUrl') + ->willReturn($basUrl); + $this + ->request + ->method('getPathInfo') + ->willReturn($pathInfo); + + $this + ->request + ->headers + ->method('get') + ->with('referer') + ->willReturn($scheme . '//' . $host . $basUrl . $pathInfo); + + $this + ->session + ->expects($this->never()) + ->method('remove') + ->with($this->gridHash); + } + + private function mockHiddenColumns() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $column1Id = 'col1Id'; + $column + ->method('getId') + ->willReturn($column1Id); + + $column2Id = 'col2Id'; + $column2 = $this->stubColumn($column2Id); + $this->grid->addColumn($column2); + + $this->grid->setHiddenColumns([$column1Id, $column2Id]); + + $column + ->expects($this->atLeastOnce()) + ->method('setVisible') + ->with(false); + + $column2 + ->expects($this->atLeastOnce()) + ->method('setVisible') + ->with(false); + } + + private function mockVisibleColumns() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $column1Id = 'col1Id'; + $column + ->method('getId') + ->willReturn($column1Id); + + $column2Id = 'col2Id'; + $column2 = $this->stubColumn($column2Id); + $this->grid->addColumn($column2); + + $column3Id = 'col3Id'; + $column3 = $this->stubColumn($column3Id); + $this->grid->addColumn($column3); + + $this->grid->setVisibleColumns([$column1Id]); + + $column2 + ->expects($this->atLeastOnce()) + ->method('setVisible') + ->with(false); + + $column3 + ->expects($this->atLeastOnce()) + ->method('setVisible') + ->with(false); + } + + private function mockColumnVisibility() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $column1Id = 'col1Id'; + $column + ->method('getId') + ->willReturn($column1Id); + + $column2Id = 'col2Id'; + $column2 = $this->stubColumn($column2Id); + $this->grid->addColumn($column2); + + $column3Id = 'col3Id'; + $column3 = $this->stubColumn($column3Id); + $this->grid->addColumn($column3); + + $column4Id = 'col4Id'; + $column4 = $this->stubColumn($column4Id); + $this->grid->addColumn($column4); + + $this->grid->showColumns([$column1Id, $column2Id]); + $this->grid->hideColumns([$column3Id, $column4Id]); + + $column + ->expects($this->atLeastOnce()) + ->method('setVisible') + ->with(true); + + $column2 + ->expects($this->atLeastOnce()) + ->method('setVisible') + ->with(true); + + $column3 + ->expects($this->atLeastOnce()) + ->method('setVisible') + ->with(false); + + $column4 + ->expects($this->atLeastOnce()) + ->method('setVisible') + ->with(false); + } + + private function mockResetPageAndLimitIfMassActionAndAllKeys() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([ + Grid::REQUEST_QUERY_MASS_ACTION => 0, + Grid::REQUEST_QUERY_MASS_ACTION_ALL_KEYS_SELECTED => true, + ]); + + $massAction = $this->stubMassActionWithCallback(function () { + }); + $this->grid->addMassAction($massAction); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => 0]); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function mockMassActionCallbackResponse() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $callbackResponse = $this + ->getMockBuilder(Response::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_MASS_ACTION => 0]); + + $massAction = $this->stubMassActionWithCallback( + fn () => $callbackResponse + ); + $this->grid->addMassAction($massAction); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => 0]); + + return $callbackResponse; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function mockMassActionControllerResponse() + { + $httpKernel = $this + ->getMockBuilder(HttpKernel::class) + ->disableOriginalConstructor() + ->getMock(); + + $subRequest = $this + ->getMockBuilder(Request::class) + ->disableOriginalConstructor() + ->getMock(); + + $callbackResponse = $this + ->getMockBuilder(Response::class) + ->disableOriginalConstructor() + ->getMock(); + + $httpKernel + ->method('handle') + ->with($subRequest, \Symfony\Component\HttpKernel\HttpKernelInterface::SUB_REQUEST) + ->willReturn($callbackResponse); + + $this->arrange(null, 'id', $httpKernel); + + $rows = new Rows(); + + $rowPrimaryFieldValue = 'pfv1'; + $row = $this->createMock(Row::class); + $row + ->method('getPrimaryFieldValue') + ->willReturn($rowPrimaryFieldValue); + $rows->addRow($row); + + $rowPrimaryFieldValue2 = 'pfv2'; + $row2 = $this->createMock(Row::class); + $row2 + ->method('getPrimaryFieldValue') + ->willReturn($rowPrimaryFieldValue2); + $rows->addRow($row2); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([ + Grid::REQUEST_QUERY_MASS_ACTION => 0, + Grid::REQUEST_QUERY_MASS_ACTION_ALL_KEYS_SELECTED => true, + ]); + + $controllerCb = 'VendorBundle:Controller:Action'; + $param1 = 'param1'; + $param1Val = 1; + $param2 = 'param2'; + $param2Val = 2; + $massAction = $this->stubMassActionWithCallback($controllerCb, [$param1 => $param1Val, $param2 => $param2Val]); + + $this + ->request + ->method('duplicate') + ->with( + [], + null, + [ + 'primaryKeys' => [$rowPrimaryFieldValue, $rowPrimaryFieldValue2], + 'allPrimaryKeys' => true, + '_controller' => $controllerCb, + $param1 => $param1Val, + $param2 => $param2Val, ] + ) + ->willReturn($subRequest); + + $this->grid->addMassAction($massAction); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => 0]); + + return $callbackResponse; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function mockExports() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_EXPORT => 0]); + + $response = $this + ->getMockBuilder(Response::class) + ->disableOriginalConstructor() + ->getMock(); + + $export = $this->createMock(Export::class); + $export + ->method('getResponse') + ->willReturn($response); + + $this->grid->addExport($export); + + $export + ->expects($this->once()) + ->method('computeData') + ->with($this->grid); + $export + ->expects($this->once()) + ->method('setContainer') + ->with($this->container); + + return $response; + } + + private function mockExportsButNotFiltersPageOrderLimit() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $colData = 'colData'; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('isFilterable') + ->willReturn(true); + $column + ->method('isSortable') + ->willReturn(true); + + $limit = 10; + $this->stubRequestWithData([ + Grid::REQUEST_QUERY_EXPORT => 0, + Grid::REQUEST_QUERY_ORDER => "$colId|ASC", + Grid::REQUEST_QUERY_LIMIT => $limit, + $colId => $colData, + ]); + + $response = $this + ->getMockBuilder(Response::class) + ->disableOriginalConstructor() + ->getMock(); + + $export = $this->createMock(Export::class); + $export + ->method('getResponse') + ->willReturn($response); + + $this->grid->setLimits($limit); + + $this->grid->addExport($export); + + $export + ->expects($this->once()) + ->method('computeData') + ->with($this->grid); + $export + ->expects($this->once()) + ->method('setContainer') + ->with($this->container); + + $this + ->session + ->expects($this->never()) + ->method('set'); + } + + private function mockTweakReset() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $title = 'aTweak'; + $tweak = ['reset' => 1]; + $tweakId = 'aValidTweakId'; + + $this->grid->addTweak($title, $tweak, $tweakId); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('remove') + ->with($this->gridHash); + } + + private function mockTweakFilters() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $colFilter = ['from' => 'foo', 'to' => 'bar']; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('getFilterType') + ->willReturn('select'); + + $title = 'aTweak'; + $tweak = ['filters' => [$colId => $colFilter]]; + $tweakId = 'aValidTweakId'; + $tweakGroup = 'tweakGroup'; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, ['tweaks' => [$tweakGroup => $tweakId], $colId => ['from' => ['foo'], 'to' => ['bar']]]); + } + + private function mockTweakOrder() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $order = 'ASC'; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('isSortable') + ->willReturn(true); + + $title = 'aTweak'; + $tweak = ['order' => "$colId|$order"]; + $tweakId = 'aValidTweakId'; + $tweakGroup = 'tweakGroup'; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, ['tweaks' => [$tweakGroup => $tweakId], Grid::REQUEST_QUERY_ORDER => "$colId|$order"]); + } + + private function mockTweakMassAction() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $title = 'aTweak'; + $tweak = ['massAction' => -1]; + $tweakId = 'aValidTweakId'; + $tweakGroup = 'tweakGroup'; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this + ->session + ->expects($this->never()) + ->method('set'); + } + + private function mockTweakPage() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + $column + ->method('isSortable') + ->willReturn(true); + + $title = 'aTweak'; + $page = 10; + $tweak = ['page' => $page]; + $tweakId = 'aValidTweakId'; + $tweakGroup = 'tweakGroup'; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, ['tweaks' => [$tweakGroup => $tweakId], Grid::REQUEST_QUERY_PAGE => $page]); + } + + private function mockTweakLimit() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + $column + ->method('isSortable') + ->willReturn(true); + + $title = 'aTweak'; + $limit = 10; + $tweak = ['limit' => $limit]; + $tweakId = 'aValidTweakId'; + $tweakGroup = 'tweakGroup'; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->grid->setLimits([$limit]); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, ['tweaks' => [$tweakGroup => $tweakId], Grid::REQUEST_QUERY_LIMIT => $limit]); + } + + private function mockTweakExport() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $title = 'aTweak'; + $tweak = ['export' => -1]; + $tweakId = 'aValidTweakId'; + $tweakGroup = 'tweakGroup'; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this + ->session + ->expects($this->never()) + ->method('set'); + } + + private function mockTweakExportButNotFiltersPageOrderLimit() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $colData = 'colData'; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('isFilterable') + ->willReturn(true); + $column + ->method('isSortable') + ->willReturn(true); + + $title = 'aTweak'; + $tweak = ['export' => -1]; + $tweakId = 'aValidTweakId'; + $tweakGroup = 'tweakGroup'; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->stubRequestWithData([ + Grid::REQUEST_QUERY_TWEAK => $tweakId, + Grid::REQUEST_QUERY_ORDER => "$colId|ASC", + Grid::REQUEST_QUERY_LIMIT => 10, + $colId => $colData, + ]); + + $this + ->session + ->expects($this->never()) + ->method('set'); + } + + private function mockRemoveActiveTweakGroups() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $order = 'ASC'; + $colFilter = ['from' => 'foo', 'to' => 'bar']; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('getFilterType') + ->willReturn('select'); + + $title = 'aTweak'; + $tweakGroup = 'tweakGroup'; + $page = 10; + $limit = 15; + $tweak = [ + 'filters' => [$colId => $colFilter], + 'order' => "$colId|$order", + 'removeActiveTweaksGroups' => $tweakGroup, + 'page' => $page, + 'limit' => $limit, + ]; + $tweakId = 'aValidTweakId'; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->grid->setLimits($limit); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [ + 'tweaks' => [], + $colId => ['from' => ['foo'], 'to' => ['bar']], + Grid::REQUEST_QUERY_PAGE => $page, + Grid::REQUEST_QUERY_LIMIT => $limit, + ]); + } + + private function mockRemoveActiveTweak() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $order = 'ASC'; + $colFilter = ['from' => 'foo', 'to' => 'bar']; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('getFilterType') + ->willReturn('select'); + + $title = 'aTweak'; + $tweakGroup = 'tweakGroup'; + $tweakId = 'aValidTweakId'; + $page = 10; + $limit = 15; + $tweak = [ + 'filters' => [$colId => $colFilter], + 'order' => "$colId|$order", + 'removeActiveTweaks' => $tweakId, + 'page' => $page, + 'limit' => $limit, + ]; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->grid->setLimits($limit); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [ + 'tweaks' => [], + $colId => ['from' => ['foo'], 'to' => ['bar']], + Grid::REQUEST_QUERY_PAGE => $page, + Grid::REQUEST_QUERY_LIMIT => $limit, + ]); + } + + private function mockAddActiveTweak() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $order = 'ASC'; + $colFilter = ['from' => 'foo', 'to' => 'bar']; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('getFilterType') + ->willReturn('select'); + + $title = 'aTweak'; + $tweakGroup = 'tweakGroup'; + $tweakId = 'aValidTweakId'; + $page = 10; + $limit = 15; + $tweak = [ + 'filters' => [$colId => $colFilter], + 'order' => "$colId|$order", + 'addActiveTweaks' => $tweakId, + 'page' => $page, + 'limit' => $limit, + ]; + + $this->grid->addTweak($title, $tweak, $tweakId, $tweakGroup); + + $this->grid->setLimits($limit); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_TWEAK => $tweakId]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [ + 'tweaks' => [$tweakGroup => $tweakId], + $colId => ['from' => ['foo'], 'to' => ['bar']], + Grid::REQUEST_QUERY_PAGE => $page, + Grid::REQUEST_QUERY_LIMIT => $limit, + ]); + } + + private function mockPageRequestData() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $page = 2; + $this->stubRequestWithData([Grid::REQUEST_QUERY_PAGE => $page]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]); + } + + private function mockPageQueryOrderRequestData() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->stubPrimaryColumn(); + $column + ->method('getId') + ->willReturn('order'); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column); + $this->grid->setColumns($columns); + + $this->stubRequestWithData([ + Grid::REQUEST_QUERY_ORDER => 'order|foo', + Grid::REQUEST_QUERY_PAGE => 2, + ]); + + $column + ->expects($this->never()) + ->method('setOrder'); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => 0]); + } + + private function mockPageLimitRequestData() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([ + Grid::REQUEST_QUERY_LIMIT => 50, + Grid::REQUEST_QUERY_PAGE => 2, + ]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => 0]); + } + + private function mockPageMassActionRequestData() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $massAction = $this->stubMassActionWithCallback(function () { + }); + $this->grid->addMassAction($massAction); + + $this->stubRequestWithData([ + Grid::REQUEST_QUERY_MASS_ACTION => 0, + Grid::REQUEST_QUERY_PAGE => 2, + ]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => 0]); + } + + private function mockPageFiltersRequestData() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $colData = 'colData'; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('isFilterable') + ->willReturn(true); + + $this->stubRequestWithData([ + Grid::REQUEST_QUERY_PAGE => 2, + $colId => $colData, + ]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [$colId => $colData, Grid::REQUEST_QUERY_PAGE => 0]); + } + + private function mockPageNotSelectFilterRequestData() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('isFilterable') + ->willReturn(true); + $column + ->method('getFilterType') + ->willReturn('differentThanSelect'); + + $page = 2; + $this->stubRequestWithData([Grid::REQUEST_QUERY_PAGE => $page]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]); + } + + private function mockPageColumnNotSelectMultiRequestData() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + + $column = $this->arrangeGridPrimaryColumn(); + + $colId = 'colId'; + $column + ->method('getId') + ->willReturn($colId); + $column + ->method('isFilterable') + ->willReturn(true); + $column + ->method('getFilterType') + ->willReturn('select'); + $column + ->method('getSelectMulti') + ->willReturn(false); + + $page = 2; + $this->stubRequestWithData([Grid::REQUEST_QUERY_PAGE => $page]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => $page]); + } + + /** + * @param string $columnId + * @param string $order + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function mockOrderRequestData($columnId, $order) + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->stubPrimaryColumn(); + $column + ->method('getId') + ->willReturn($columnId); + $column + ->method('isSortable') + ->willReturn(true); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column); + $this->grid->setColumns($columns); + + $queryOrder = "$columnId|$order"; + $this->stubRequestWithData([Grid::REQUEST_QUERY_ORDER => $queryOrder]); + + return $column; + } + + private function mockOrderColumnNotSortable() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $columnId = 'columnId'; + + $column = $this->stubPrimaryColumn(); + $column + ->method('getId') + ->willReturn($columnId); + $column + ->method('isSortable') + ->willReturn(false); + + $columns = new Columns($this->authChecker); + $columns->addColumn($column); + $this->grid->setColumns($columns); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_ORDER => $columnId . '|asc']); + + $column + ->expects($this->never()) + ->method('setOrder'); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => 0]); + } + + private function mockConfiguredLimitRequestData() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $limit = 10; + $this->stubRequestWithData([Grid::REQUEST_QUERY_LIMIT => $limit]); + + $this->grid->setLimits($limit); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_LIMIT => $limit, Grid::REQUEST_QUERY_PAGE => 0]); + } + + private function mockNonConfiguredLimitRequestData() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $this->stubRequestWithData([Grid::REQUEST_QUERY_LIMIT => 10]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => 0]); + } + + private function mockDefaultSessionFiltersWithoutRequestData() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $col1Id = 'col1'; + $col2Id = 'col2'; + $col3Id = 'col3'; + $col4Id = 'col4'; + $col5Id = 'col5'; + + $col1FilterValue = 'val1'; + $col2FilterValue = ['val2']; + + $col5From = 'foo'; + $col5To = 'bar'; + + [$column1, $column2, $column3, $column4, $column5] = $this->arrangeColumnsFilters( + $col1Id, + $col2Id, + $col3Id, + $col4Id, + $col5Id, + $col1FilterValue, + $col2FilterValue, + $col5From, + $col5To + ); + + $column + ->expects($this->never()) + ->method('setData') + ->with($this->anything()); + $column1 + ->expects($this->once()) + ->method('setData') + ->with(['from' => $col1FilterValue]); + $column2 + ->expects($this->once()) + ->method('setData') + ->with(['from' => $col2FilterValue]); + $column3 + ->expects($this->once()) + ->method('setData') + ->with(['from' => 1]); + $column4 + ->expects($this->once()) + ->method('setData') + ->with(['from' => 0]); + $column5 + ->expects($this->once()) + ->method('setData') + ->with(['from' => [$col5From], 'to' => [$col5To]]); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [ + $col1Id => ['from' => $col1FilterValue], + $col2Id => ['from' => $col2FilterValue], + $col3Id => ['from' => 1], + $col4Id => ['from' => 0], + $col5Id => ['from' => [$col5From], 'to' => [$col5To]], + ]); + } + + /** + * @param string $col1Id + * @param string $col2Id + * @param string $col3Id + * @param string $col4Id + * @param string $col5Id + * @param string $col1FilterValue + * @param array $col2FilterValue + * @param string $col5From + * @param string $col5To + * + * @return array + */ + private function arrangeColumnsFilters( + $col1Id, + $col2Id, + $col3Id, + $col4Id, + $col5Id, + $col1FilterValue, + $col2FilterValue, + $col5From, + $col5To + ) { + $column1 = $this->stubColumn($col1Id); + $this->grid->addColumn($column1); + + $column2 = $this->stubColumn($col2Id); + $this->grid->addColumn($column2); + + $col3FilterValue = ['from' => true]; + $column3 = $this->stubColumn($col3Id); + $this->grid->addColumn($column3); + + $col4FilterValue = ['from' => false]; + $column4 = $this->stubColumn($col4Id); + $this->grid->addColumn($column4); + + $col5FilterValue = ['from' => $col5From, 'to' => $col5To]; + $column5 = $this + ->getMockBuilder(Column::class) + ->disableOriginalConstructor() + ->getMock(); + $column5 + ->method('getId') + ->willReturn($col5Id); + $column5 + ->method('getFilterType') + ->willReturn('select'); + + $this->grid->addColumn($column5); + + $this->grid->setDefaultFilters([ + $col1Id => $col1FilterValue, + $col2Id => $col2FilterValue, + $col3Id => $col3FilterValue, + $col4Id => $col4FilterValue, + $col5Id => $col5FilterValue, + ]); + + return [$column1, $column2, $column3, $column4, $column5]; + } + + private function mockDefaultPage() + { + $row = $this->createMock(Row::class); + $rows = new Rows(); + $rows->addRow($row); + + $this->arrangeGridSourceDataLoadedWithRows($rows); + $this->arrangeGridPrimaryColumn(); + + $this->grid->setDefaultPage(2); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_PAGE => 1]); + } + + /** + * @param string $order + */ + private function mockDefaultOrder($order) + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + + $column = $this->arrangeGridPrimaryColumn(); + + $columnId = 'columnId'; + $column + ->method('getId') + ->willReturn('columnId'); + + $this->grid->setDefaultOrder($columnId, $order); + + $column + ->expects($this->once()) + ->method('setOrder') + ->with($order); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_ORDER => "$columnId|$order"]); + } + + private function mockDefaultLimit() + { + $this->arrangeGridSourceDataLoadedWithEmptyRows(); + $this->arrangeGridPrimaryColumn(); + + $limit = 2; + $this->grid->setLimits([$limit => "$limit"]); + $this->grid->setDefaultLimit($limit); + + $this + ->session + ->expects($this->atLeastOnce()) + ->method('set') + ->with($this->gridHash, [Grid::REQUEST_QUERY_LIMIT => $limit]); + } + + /** + * @param int $totalCount + * @param string $sourceHash + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function arrangeGridSourceDataLoadedWithEmptyRows($totalCount = 0, $sourceHash = null) + { + $source = $this->createMock(Source::class); + $source + ->method('isDataLoaded') + ->willReturn(true); + $source + ->method('executeFromData') + ->willReturn(new Rows()); + $source + ->method('getTotalCountFromData') + ->willReturn($totalCount); + $source + ->method('getHash') + ->willReturn($sourceHash); + + $this->grid->setSource($source); + + return $source; + } + + /** + * @param int $totalCount + */ + private function arrangeGridSourceDataLoadedWithRows(Rows $rows, $totalCount = 0) + { + $source = $this->createMock(Source::class); + $source + ->method('isDataLoaded') + ->willReturn(true); + $source + ->method('executeFromData') + ->willReturn($rows); + $source + ->method('getTotalCountFromData') + ->willReturn($totalCount); + + $this->grid->setSource($source); + } + + /** + * @param int $totalCount + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function arrangeGridSourceDataLoadedWithoutRowsReturned($totalCount = 0) + { + $source = $this->createMock(Source::class); + $source + ->method('isDataLoaded') + ->willReturn(true); + $source + ->method('getTotalCountFromData') + ->willReturn($totalCount); + + $this->grid->setSource($source); + + return $source; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function arrangeGridSourceDataNotLoadedWithoutRowsReturned() + { + $source = $this->createMock(Source::class); + $source + ->method('isDataLoaded') + ->willReturn(false); + $source + ->method('getTotalCount') + ->willReturn(0); + + $this->grid->setSource($source); + + return $source; + } + + /** + * @param int $totalCount + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function arrangeGridSourceDataNotLoadedWithEmptyRows($totalCount = 0) + { + $source = $this->createMock(Source::class); + $source + ->method('isDataLoaded') + ->willReturn(false); + $source + ->method('getTotalCount') + ->willReturn($totalCount); + $source + ->method('execute') + ->willReturn(new Rows()); + + $this->grid->setSource($source); + + return $source; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function arrangeGridPrimaryColumn() + { + $column = $this->stubPrimaryColumn(); + $this->grid->addColumn($column); + + return $column; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function stubPrimaryColumn() + { + $column = $this + ->getMockBuilder(Column::class) + ->disableOriginalConstructor() + ->getMock(); + $column + ->method('isPrimary') + ->willReturn(true); + + return $column; + } + + /** + * @param string $columnId + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function stubFilteredColumn($columnId = null) + { + $column = $this->stubColumn($columnId); + $column + ->method('isFiltered') + ->willReturn(true); + + return $column; + } + + /** + * @param mixed $columnId + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function stubTitledColumn($columnId = null) + { + $column = $this->stubColumn($columnId); + $column + ->method('getTitle') + ->willReturn(true); + + return $column; + } + + /** + * @param string $type + * @param mixed $columnId + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function stubFilterableColumn($type, $columnId = null) + { + $column = $this->stubColumn($columnId); + $column + ->method('isFilterable') + ->willReturn(true); + $column + ->method('getType') + ->willReturn($type); + + return $column; + } + + /** + * @param string $defaultOp + * @param mixed $columnId + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function stubColumnWithDefaultOperator($defaultOp, $columnId = null) + { + $column = $this->stubColumn($columnId); + $column + ->method('getDefaultOperator') + ->willReturn($defaultOp); + + return $column; + } + + /** + * @param mixed $columnId + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function stubColumn($columnId = null) + { + $column = $this + ->getMockBuilder(Column::class) + ->disableOriginalConstructor() + ->getMock(); + $column + ->method('getId') + ->willReturn($columnId); + + return $column; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function arrangeGridWithColumnsIterator() + { + $column = $this->stubColumn('primaryID'); + + $columnIterator = $this + ->getMockBuilder(ColumnsIterator::class) + ->disableOriginalConstructor() + ->getMock(); + + $columns = $this + ->getMockBuilder(Columns::class) + ->disableOriginalConstructor() + ->getMock(); + $columns + ->method('getIterator') + ->willReturn($columnIterator); + $columns + ->method('getPrimaryColumn') + ->willReturn($column); + + $this->grid->setColumns($columns); + + return $columns; + } + + /** + * @param mixed $aCallback + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function stubMassActionWithCallback($aCallback, array $params = []) + { + $massAction = $this->stubMassAction(); + $massAction + ->method('getCallback') + ->willReturn($aCallback); + $massAction + ->method('getParameters') + ->willReturn($params); + + return $massAction; + } + + /** + * @param string $role + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function stubMassAction($role = null) + { + // @todo: It seems that MassActionInterface does not have getRole in it. is that fine? + $massAction = $this + ->getMockBuilder(MassAction::class) + ->disableOriginalConstructor() + ->getMock(); + $massAction + ->method('getRole') + ->willReturn($role); + + return $massAction; + } + + /** + * @param string $role + * @param mixed $colId + * + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function stubRowAction($role = null, $colId = null) + { + // @todo: It seems that RowActionInterface does not have getRole in it. is that fine? + $rowAction = $this + ->getMockBuilder(RowAction::class) + ->disableOriginalConstructor() + ->getMock(); + $rowAction + ->method('getRole') + ->willReturn($role); + $rowAction + ->method('getColumn') + ->willReturn($colId); + + return $rowAction; + } + + private function stubRequestWithData(array $requestData) + { + $this + ->request + ->method('get') + ->with($this->gridHash) + ->willReturn($requestData); + } + + /** + * @param int $tweakPage + * + * @return array + */ + private function arrangeDefaultTweaks($tweakPage) + { + $group = 'aGroup'; + $title = 'aTweak'; + $tweak = ['page' => $tweakPage, 'group' => $group]; + $tweakId = 'aValidTweakId'; + + $this->grid->addTweak($title, $tweak, $tweakId); + + $this->grid->setDefaultTweak($tweakId); + + return [$group, $tweakId]; + } +} diff --git a/Tests/Grid/Helper/ColumnsIteratorTest.php b/Tests/Grid/Helper/ColumnsIteratorTest.php new file mode 100644 index 00000000..dce0e726 --- /dev/null +++ b/Tests/Grid/Helper/ColumnsIteratorTest.php @@ -0,0 +1,58 @@ +setUpMocks(); + $columnsIterator = new ColumnsIterator($this->iterator, false); + + $this->assertTrue($columnsIterator->accept()); + } + + public function testAcceptSourceColumnThatsVisibile() + { + $this->setUpMocks(true); + $columnsIterator = new ColumnsIterator($this->iterator, true); + + $this->assertTrue($columnsIterator->accept()); + } + + public function testNotAcceptSourceColumnThatsNotVisibile() + { + $this->setUpMocks(false); + $columnsIterator = new ColumnsIterator($this->iterator, true); + + $this->assertFalse($columnsIterator->accept()); + } + + /** + * @param null|bool $isVisibleForSource + */ + protected function setUpMocks($isVisibleForSource = null) + { + $column = $this->getMockBuilder(Column::class) + ->disableOriginalConstructor() + ->getMock(); + + if (null === $isVisibleForSource) { + $column->expects($this->never())->method('isVisibleForSource'); + } else { + $column->expects($this->any())->method('isVisibleForSource')->willReturn($isVisibleForSource); + } + + $this->iterator = $this->getMockBuilder(\Iterator::class) + ->disableOriginalConstructor() + ->getMock(); + $this->iterator->expects($this->any())->method('current')->willReturn($column); + } +} diff --git a/Tests/Grid/Mapping/ColumnTest.php b/Tests/Grid/Mapping/ColumnTest.php new file mode 100644 index 00000000..219052b1 --- /dev/null +++ b/Tests/Grid/Mapping/ColumnTest.php @@ -0,0 +1,34 @@ +stringMetadata = 'foo'; + $this->arrayMetadata = ['foo' => 'bar', 'groups' => 'baz']; + } + + public function testColumnMetadataCanBeEmpty() + { + $column = new Column([]); + $this->assertEmpty($column->getMetadata()); + $this->assertEquals(['default'], $column->getGroups()); + } + + public function testColumnStringMetadataInjectedInConstructor() + { + $column = new Column($this->stringMetadata); + $this->assertEquals($this->stringMetadata, $column->getMetadata()); + } + + public function testColumnArrayMetadataInjectedInConstructor() + { + $column = new Column($this->arrayMetadata); + $this->assertEquals($this->arrayMetadata, $column->getMetadata()); + } +} diff --git a/Tests/Grid/Mapping/Metadata/DriverHeapTest.php b/Tests/Grid/Mapping/Metadata/DriverHeapTest.php new file mode 100644 index 00000000..0874bd60 --- /dev/null +++ b/Tests/Grid/Mapping/Metadata/DriverHeapTest.php @@ -0,0 +1,38 @@ +assertEquals(0, $driverHeap->compare($priority1, $priority2)); + } + + public function testPriority1MoreThanPriority2() + { + $priority1 = 100; + $priority2 = 1; + + $driverHeap = new DriverHeap(); + + $this->assertEquals(-1, $driverHeap->compare($priority1, $priority2)); + } + + public function testPriority1LessThanPriority2() + { + $priority1 = 1; + $priority2 = 100; + + $driverHeap = new DriverHeap(); + + $this->assertEquals(1, $driverHeap->compare($priority1, $priority2)); + } +} diff --git a/Tests/Grid/Mapping/Metadata/ManagerTest.php b/Tests/Grid/Mapping/Metadata/ManagerTest.php new file mode 100644 index 00000000..b775feeb --- /dev/null +++ b/Tests/Grid/Mapping/Metadata/ManagerTest.php @@ -0,0 +1,93 @@ +manager = new Manager(); + } + + public function testAddDriver() + { + $driverInterfaceMock = $this->createMock(DriverInterface::class); + $priority = 1; + + $driverHeap = new DriverHeap(); + $driverHeap->insert($driverInterfaceMock, $priority); + + $this->manager->addDriver($driverInterfaceMock, $priority); + + $this->assertEquals($driverHeap, $this->manager->getDrivers()); + } + + public function testGetDrivers() + { + $driverInterfaceMock = $this->createMock(DriverInterface::class); + + $priority = 1; + $driverHeap = new DriverHeap(); + $driverHeap->insert($driverInterfaceMock, $priority); + + $this->manager->addDriver($driverInterfaceMock, $priority); + $drivers = $this->manager->getDrivers(); + + $this->assertEquals($driverHeap, $drivers); + } + + public function testGetDriversReturnDifferentClone() + { + $driverFirstTime = $this->manager->getDrivers(); + $driverSecondTime = $this->manager->getDrivers(); + + $this->assertNotSame($driverFirstTime, $driverSecondTime); + } + + public function testGetMetadataWithoutDrivers() + { + $cols = []; + $mappings = []; + $groupBy = []; + + $metadataExpected = new Metadata(); + $metadataExpected->setFields($cols); + $metadataExpected->setFieldsMappings($mappings); + $metadataExpected->setGroupBy($groupBy); + + $metadata = $this->manager->getMetadata('foo', 'bar'); + + $this->assertEquals($metadataExpected, $metadata); + } + + public function testGetMetadata() + { + $fields = ['0' => 'bar']; + $groupBy = ['foo' => 'bar']; + $mapping = ['bar' => ['foo' => 'foo2']]; + + $driverInterfaceMock = $this->createMock(DriverInterface::class); + $driverInterfaceMock->method('getClassColumns') + ->willReturn($fields); + + $driverInterfaceMock->method('getFieldsMetadata') + ->willReturn($mapping); + + $driverInterfaceMock->method('getGroupBy') + ->willReturn($groupBy); + + $this->manager->addDriver($driverInterfaceMock, 1); + + $metadata = $this->manager->getMetadata('foo'); + + $this->assertEquals($fields, $metadata->getFields()); + $this->assertEquals($groupBy, $metadata->getGroupBy()); + $this->assertEquals($mapping, $metadata->getFieldsMappings()); + } +} diff --git a/Tests/Grid/Mapping/Metadata/MetadataTest.php b/Tests/Grid/Mapping/Metadata/MetadataTest.php new file mode 100644 index 00000000..9a0426ef --- /dev/null +++ b/Tests/Grid/Mapping/Metadata/MetadataTest.php @@ -0,0 +1,164 @@ +metadata = new Metadata(); + } + + public function testSetFields() + { + $field = ['foo' => 'bar']; + + $this->metadata->setFields($field); + + $this->assertEquals($field, $this->metadata->getFields()); + } + + public function testGetFields() + { + $field = ['foo' => 'bar']; + + $this->metadata->setFields($field); + + $this->assertEquals($field, $this->metadata->getFields()); + } + + public function testHasFieldMappingWithField() + { + $field = 'foo'; + $value = 'bar'; + $fieldMapping = [$field => ['type' => $value]]; + + $this->metadata->setFieldsMappings($fieldMapping); + + $this->assertTrue($this->metadata->hasFieldMapping($field)); + $this->assertFalse($this->metadata->hasFieldMapping('notAddedField')); + } + + public function testGetterFieldMappingReturnDefaultTypeText() + { + $field = 'foo'; + $value = 'bar'; + $fieldMapping = [$field => $value]; + + $this->metadata->setFieldsMappings($fieldMapping); + + $this->assertEquals('text', $this->metadata->getFieldMappingType($field)); + } + + public function testSetterMappingFieldWithType() + { + $field = 'foo'; + $value = 'bar'; + $fieldMapping = [$field => ['type' => $value]]; + + $this->metadata->setFieldsMappings($fieldMapping); + + $this->assertEquals($fieldMapping, $this->metadata->getFieldsMappings()); + } + + public function testGetterMappingFieldWithType() + { + $field = 'foo'; + $value = 'bar'; + $fieldMapping = [$field => ['type' => $value]]; + + $this->metadata->setFieldsMappings($fieldMapping); + $this->assertEquals($value, $this->metadata->getFieldMappingType($field)); + } + + public function testSetterGroupBy() + { + $groupBy = 'groupBy'; + + $this->metadata->setGroupBy($groupBy); + + $this->assertEquals($groupBy, $this->metadata->getGroupBy()); + } + + public function testGetterGroupBy() + { + $groupBy = 'groupBy'; + + $this->metadata->setGroupBy($groupBy); + $this->assertEquals($groupBy, $this->metadata->getGroupBy()); + } + + public function testSetterName() + { + $name = 'name'; + + $this->metadata->setName($name); + + $this->assertEquals($name, $this->metadata->getName()); + } + + public function testGetterName() + { + $name = 'name'; + + $this->metadata->setName($name); + + $this->assertEquals($name, $this->metadata->getName()); + } + + public function testGetColumnsFromMappingWithoutTypeReturnException() + { + $this->expectException(\Exception::class); + + $field = 'foo'; + $value = 'bar'; + $fieldMapping = [$field => ['type' => $value]]; + + $columnsMock = $this->createMock(Columns::class); + $columnsMock->method('hasExtensionForColumnType') + ->with($value) + ->willReturn(false); + + $this->metadata->setFields(['foo' => $field]); + $this->metadata->setFieldsMappings($fieldMapping); + $this->metadata->getColumnsFromMapping($columnsMock); + } + + public function testGetColumnsFromMapping() + { + $field = 'foo'; + $field2 = 'foo2'; + $value = 'bar'; + $value2 = 'bar'; + $fieldMapping = [ + $field => [ + 'type' => $value, + ], + $field2 => [ + 'type' => $value2, + ], + ]; + + $columnsMockClone = $this->getMockForAbstractClass(Column::class); + + $columnsMock = $this->createMock(Columns::class); + $columnsMock->method('hasExtensionForColumnType') + ->with($value) + ->willReturn(true); + + $columnsMock->method('getExtensionForColumnType') + ->with($value) + ->willReturn($columnsMockClone); + + $this->metadata->setFields(['foo' => $field]); + $this->metadata->setFieldsMappings($fieldMapping); + $columns = $this->metadata->getColumnsFromMapping($columnsMock); + + $this->assertInstanceOf(\SplObjectStorage::class, $columns); + } +} diff --git a/Tests/Grid/Mapping/SourceTest.php b/Tests/Grid/Mapping/SourceTest.php new file mode 100644 index 00000000..f8495896 --- /dev/null +++ b/Tests/Grid/Mapping/SourceTest.php @@ -0,0 +1,146 @@ +source = new Source([]); + } + + public function testColumnsHasDefaultValue() + { + $this->assertEquals([], $this->source->getColumns()); + } + + public function testFilterableHasDefaultValue() + { + $this->assertEquals(true, $this->source->isFilterable()); + } + + public function testSortableHasDefaultValue() + { + $this->assertEquals(true, $this->source->isSortable()); + } + + public function testGroupsHasDefaultValue() + { + $expectedGroups = ['0' => 'default']; + + $this->assertEquals($expectedGroups, $this->source->getGroups()); + } + + public function testGroupByHasDefaultValue() + { + $this->assertEquals([], $this->source->getGroupBy()); + } + + public function testSetterColumns() + { + $columns = 'columns'; + $expectedColumns = [$columns]; + + $this->source = new Source(['columns' => $columns]); + + $this->assertEquals($expectedColumns, $this->source->getColumns()); + } + + public function testGetterColumns() + { + $columns = 'columns'; + $expectedColumns = [$columns]; + + $this->source = new Source(['columns' => $columns]); + + $this->assertEquals($expectedColumns, $this->source->getColumns()); + } + + public function testGetterHasColumns() + { + $columns = 'columns'; + + $this->source = new Source(['columns' => $columns]); + + $this->assertTrue($this->source->hasColumns()); + } + + public function testSetterFilterable() + { + $filterable = false; + + $this->source = new Source(['filterable' => $filterable]); + + $this->assertEquals($filterable, $this->source->isFilterable()); + } + + public function testGetterFilterable() + { + $filterable = false; + + $this->source = new Source(['filterable' => $filterable]); + + $this->assertEquals($filterable, $this->source->isFilterable()); + } + + public function testSetterSortable() + { + $sortable = false; + + $this->source = new Source(['sortable' => $sortable]); + + $this->assertEquals($sortable, $this->source->isSortable()); + } + + public function testGetterSortable() + { + $sortable = false; + + $this->source = new Source(['sortable' => $sortable]); + + $this->assertEquals($sortable, $this->source->isSortable()); + } + + public function testSetterGroups() + { + $groups = 'groups'; + $expectedGroups = [$groups]; + + $this->source = new Source(['groups' => $groups]); + + $this->assertEquals($expectedGroups, $this->source->getGroups()); + } + + public function testGetterGroups() + { + $groups = 'groups'; + $expectedGroups = [$groups]; + + $this->source = new Source(['groups' => $groups]); + + $this->assertEquals($expectedGroups, $this->source->getGroups()); + } + + public function testSetterGroupBy() + { + $groupsBy = 'groupBy'; + $expectedGroupsBy = [$groupsBy]; + + $this->source = new Source(['groupBy' => $groupsBy]); + + $this->assertEquals($expectedGroupsBy, $this->source->getGroupBy()); + } + + public function testGetterGroupBy() + { + $groupsBy = 'groupBy'; + $expectedGroupsBy = [$groupsBy]; + + $this->source = new Source(['groupBy' => $groupsBy]); + + $this->assertEquals($expectedGroupsBy, $this->source->getGroupBy()); + } +} diff --git a/Tests/Grid/RowTest.php b/Tests/Grid/RowTest.php new file mode 100644 index 00000000..b0f4c720 --- /dev/null +++ b/Tests/Grid/RowTest.php @@ -0,0 +1,209 @@ +createMock(EntityRepository::class); + $this->row->setRepository($repo); + + $this->assertSame($repo, $this->row->getRepository()); + } + + public function testSetPrimaryField() + { + $pf = 'id'; + $this->row->setPrimaryField($pf); + + $this->assertEquals($pf, $this->row->getPrimaryField()); + } + + public function testGetPrimaryField() + { + $pf = 'id'; + $this->row->setPrimaryField($pf); + + $this->assertEquals($pf, $this->row->getPrimaryField()); + } + + public function testSetField() + { + $field1Id = 'col1'; + $field1Val = 'col1_val'; + + $field2Id = 'col2'; + $field2Val = 'col2_val'; + + $this->row->setField($field1Id, $field1Val); + $this->row->setField($field2Id, $field2Val); + + $this->assertEquals([$field1Id => $field1Val, $field2Id => $field2Val], $this->row->getFields()); + } + + public function testGetField() + { + $field = 'col1'; + $val = 'col1_val'; + + $this->row->setField($field, $val); + + $this->assertEquals($val, $this->row->getField($field)); + $this->assertEmpty($this->row->getField('col2')); + } + + public function testGetPrimaryFieldValueWithoutDefiningIt() + { + $this->expectException(\InvalidArgumentException::class); + + $this->row->getPrimaryFieldValue(); + } + + public function testGetPrimaryFieldValueWithoutAddingItToFields() + { + $this->expectException(\InvalidArgumentException::class); + + $field = 'foo'; + $fieldValue = 1; + $primaryField = 'id'; + + $this->row->setField($field, $fieldValue); + $this->row->setPrimaryField($primaryField); + + $this->row->getPrimaryFieldValue(); + } + + public function testGetSinglePrimaryFieldValue() + { + $field = 'id'; + $value = 1; + $primaryField = 'id'; + + $this->row->setField($field, $value); + $this->row->setPrimaryField($primaryField); + + $this->assertEquals($value, $this->row->getPrimaryFieldValue()); + } + + public function testGetArrayPrimaryFieldsValue() + { + $field1 = 'id'; + $value1 = 1; + + $field2 = 'foo'; + $value2 = 'foo_value'; + + $this->row->setField($field1, $value1); + $this->row->setField($field2, $value2); + $this->row->setPrimaryField([$field1, $field2]); + + $this->assertEquals([$field1 => $value1, $field2 => $value2], $this->row->getPrimaryFieldValue()); + } + + public function testGetSinglePrimaryKeyValue() + { + $field = 'foo'; + $value = 1; + + $this->row->setField($field, $value); + $this->row->setPrimaryField($field); + + // @todo: as you can see, primary field named foo is now translated to id: is that correct? + $this->assertEquals(['id' => $value], $this->row->getPrimaryKeyValue()); + } + + public function testGetCompositePrimaryKeyValue() + { + $field1 = 'foo'; + $value1 = 1; + + $field2 = 'bar'; + $value2 = 2; + + $this->row->setField($field1, $value1); + $this->row->setField($field2, $value2); + $this->row->setPrimaryField([$field1, $field2]); + + $this->assertEquals([$field1 => $value1, $field2 => $value2], $this->row->getPrimaryKeyValue()); + } + + public function testGetEntity() + { + $field = 'foo'; + $value = 1; + + $this->row->setField($field, $value); + $this->row->setPrimaryField($field); + + $entityDummy = $this->createMock(self::class); + $repo = $this->createMock(EntityRepository::class); + $repo + ->expects($this->once()) + ->method('find') + ->with($value) + ->willReturn($entityDummy); + $this->row->setRepository($repo); + + $this->assertSame($entityDummy, $this->row->getEntity()); + } + + public function testSetClass() + { + $class = 'Vendor/Bundle/Foo'; + $this->row->setClass($class); + + $this->assertEquals($class, $this->row->getClass()); + } + + public function testGetClass() + { + $class = 'Vendor/Bundle/Foo'; + $this->row->setClass($class); + + $this->assertEquals($class, $this->row->getClass()); + } + + public function testSetColor() + { + $color = 'red'; + $this->row->setColor($color); + + $this->assertEquals($color, $this->row->getColor()); + } + + public function testGetColor() + { + $color = 'blue'; + $this->row->setColor($color); + + $this->assertEquals($color, $this->row->getColor()); + } + + public function testSetLegend() + { + $legend = 'foo'; + $this->row->setLegend($legend); + + $this->assertEquals($legend, $this->row->getLegend()); + } + + public function testGetLegend() + { + $legend = 'bar'; + $this->row->setLegend($legend); + + $this->assertEquals($legend, $this->row->getLegend()); + } + + public function setUp(): void + { + $this->row = new Row(); + } +} diff --git a/Tests/Grid/RowsTest.php b/Tests/Grid/RowsTest.php new file mode 100644 index 00000000..12ed8705 --- /dev/null +++ b/Tests/Grid/RowsTest.php @@ -0,0 +1,41 @@ +assertEquals(3, $this->rowsSUT->count()); + } + + public function testGetIterator() + { + $this->assertInstanceOf(\SplObjectStorage::class, $this->rowsSUT->getIterator()); + } + + public function testAddRow() + { + $this->rowsSUT->addRow($this->createMock(Row::class)); + $this->assertEquals(4, $this->rowsSUT->count()); + } + + public function testToArray() + { + $this->assertEquals($this->rows, $this->rowsSUT->toArray()); + } + + public function setUp(): void + { + $this->rows = [$this->createMock(Row::class), $this->createMock(Row::class), $this->createMock(Row::class)]; + $this->rowsSUT = new Rows($this->rows); + } +} diff --git a/Tests/Grid/Source/VectorTest.php b/Tests/Grid/Source/VectorTest.php new file mode 100644 index 00000000..aecece0d --- /dev/null +++ b/Tests/Grid/Source/VectorTest.php @@ -0,0 +1,252 @@ +assertEmpty($this->vector->getData()); + } + + public function testRaiseExceptionDuringVectorCreationWhenDataIsNotAVector() + { + $this->expectException(\InvalidArgumentException::class); + + new Vector(['notAnArray'], []); + } + + public function testRaiseExceptionDuringVectorCreationWhenEmptyVector() + { + $this->expectException(\InvalidArgumentException::class); + + new Vector([[]], []); + } + + public function testCreateVectorWithColumns() + { + $column = $this->createMock(Column::class); + $column2 = $this->createMock(Column::class); + $columns = [$column, $column2]; + + $vector = new Vector([], $columns); + + //$this->assertEquals($columns, $vector->getColumns()); + } + + public function testInitialiseWithoutData() + { + $this->vector->initialise($this->createMock(Container::class)); + + //$this->assertEmpty($this->vector->getColumns()); + } + + public function testInizialiseWithGuessedColumnsMergedToAlreadySettedColumns() + { + $columnId = 'cId'; + $column = $this->createMock(Column::class); + $column + ->method('getId') + ->willReturn($columnId); + + $column2Id = 'c2Id'; + $column2 = $this->createMock(Column::class); + $column2 + ->method('getId') + ->willReturn($column2Id); + + $vector = new Vector([['c3Id' => 'c3', 'c4Id' => 'c4']], [$column, $column2]); + + $uc1 = new UntypedColumn([ + 'id' => 'c3Id', + 'title' => 'c3Id', + 'source' => true, + 'filterable' => true, + 'sortable' => true, + 'visible' => true, + 'field' => 'c3Id', + ]); + $uc1->setType('text'); + + $uc2 = new UntypedColumn([ + 'id' => 'c4Id', + 'title' => 'c4Id', + 'source' => true, + 'filterable' => true, + 'sortable' => true, + 'visible' => true, + 'field' => 'c4Id', + ]); + $uc2->setType('text'); + + $vector->initialise($this->createMock(Container::class)); + + //$this->assertEquals([$column, $column2, $uc1, $uc2], $vector->getColumns()); + } + + public function testInizialiseWithoutGuessedColumns() + { + $columnId = 'cId'; + $column = $this->createMock(Column::class); + $column + ->method('getId') + ->willReturn($columnId); + + $column2Id = 'c2Id'; + $column2 = $this->createMock(Column::class); + $column2 + ->method('getId') + ->willReturn($column2Id); + + $vector = new Vector([[$columnId => 'c1', $column2Id => 'c2']], [$column, $column2]); + + $vector->initialise($this->createMock(Container::class)); + + //$this->assertEquals([$column, $column2], $vector->getColumns()); + } + + /** + * @dataProvider guessedColumnProvider + */ + public function testInizializeWithGuessedColumn($vectorValue, UntypedColumn $untypedColumn, $columnType) + { + $untypedColumn->setType($columnType); + + $vector = new Vector($vectorValue); + $vector->initialise($this->createMock(Container::class)); + + //$this->assertEquals([$untypedColumn], $vector->getColumns()); + } + + public function testExecute() + { + $rows = [new Row(), new Row()]; + $columns = $this->createMock(Columns::class); + + $vector = $this->createPartialMock(Vector::class, ['executeFromData']); + $vector + ->method('executeFromData') + ->with($columns, 0, null, null) + ->willReturn($rows); + + $this->assertEquals($rows, $vector->execute($columns, 0, null, null)); + } + + public function testPopulateSelectFilters() + { + $columns = $this->createMock(Columns::class); + + $vector = $this->createPartialMock(Vector::class, ['populateSelectFiltersFromData']); + $vector + ->expects($this->once()) + ->method('populateSelectFiltersFromData') + ->with($columns, false); + + $vector->populateSelectFilters($columns); + } + + public function testGetTotalCount() + { + $maxResults = 10; + + $vector = $this->createPartialMock(Vector::class, ['getTotalCountFromData']); + $vector + ->method('getTotalCountFromData') + ->with($maxResults) + ->willReturn(8); + + $this->assertEquals(8, $vector->getTotalCount($maxResults)); + } + + public function testGetHash() + { + $idCol1 = 'idCol1'; + $column1 = $this->createMock(Column::class); + $column1 + ->method('getId') + ->willReturn($idCol1); + + $idCol2 = 'idCol2'; + $column2 = $this->createMock(Column::class); + $column2 + ->method('getId') + ->willReturn($idCol2); + + $vector = new Vector([], [$column1, $column2]); + + $this->assertEquals(\APY\DataGridBundle\Grid\Source\Vector::class . md5($idCol1.$idCol2), $vector->getHash()); + } + + public function testSetId() + { + $id = 'id'; + $this->vector->setId($id); + + $this->assertEquals($id, $this->vector->getId()); + } + + public function testGetId() + { + $id = 'id'; + $this->vector->setId($id); + + $this->assertEquals($id, $this->vector->getId()); + } + + public function guessedColumnProvider() + { + $uc = new UntypedColumn([ + 'id' => 'c1Id', + 'title' => 'c1Id', + 'source' => true, + 'filterable' => true, + 'sortable' => true, + 'visible' => true, + 'field' => 'c1Id', + ]); + + $date = new \DateTime(); + $date->setTime(0, 0, 0); + + return [ + 'Empty' => [[['c1Id' => '']], $uc, 'text'], + 'Null' => [[['c1Id' => null]], $uc, 'text'], + 'Array' => [[['c1Id' => []]], $uc, 'array'], + 'Datetime' => [[['c1Id' => new \DateTime()]], $uc, 'datetime'], + 'Date' => [[['c1Id' => $date]], $uc, 'date'], + 'String but not date' => [[['c1Id' => 'thisIsAString']], $uc, 'text'], + 'Date string' => [[['c1Id' => '2017-07-22']], $uc, 'date'], + 'Datetime string' => [[['c1Id' => '2017-07-22 12:00:00']], $uc, 'datetime'], + 'True value' => [[['c1Id' => true]], $uc, 'boolean'], + 'False value' => [[['c1Id' => true]], $uc, 'boolean'], + 'True int value' => [[['c1Id' => 1]], $uc, 'boolean'], + 'False int value' => [[['c1Id' => 0]], $uc, 'boolean'], + 'True string value' => [[['c1Id' => '1']], $uc, 'boolean'], + 'False string value' => [[['c1Id' => '0']], $uc, 'boolean'], + 'Number' => [[['c1Id' => 12]], $uc, 'number'], + 'Boolean and not number' => [[['c1Id' => true], ['c1Id' => '2017-07-22']], $uc, 'text'], + 'Boolean and number' => [[['c1Id' => true], ['c1Id' => 20]], $uc, 'number'], + 'Date and not date time' => [[['c1Id' => '2017-07-22'], ['c1Id' => 20]], $uc, 'text'], + 'Date and time' => [[['c1Id' => '2017-07-22'], ['c1Id' => '2017-07-22 11:00:00']], $uc, 'datetime'] + ]; + } + + public function setUp(): void + { + $this->vector = new Vector([], []); + } +} + +class VectorObj +{ +} diff --git a/Tests/Hook/BypassFinalHook.php b/Tests/Hook/BypassFinalHook.php new file mode 100644 index 00000000..44be3e25 --- /dev/null +++ b/Tests/Hook/BypassFinalHook.php @@ -0,0 +1,14 @@ +assertTrue(true); } -} \ No newline at end of file +} diff --git a/Tests/Twig/DataGridExtensionTest.php b/Tests/Twig/DataGridExtensionTest.php new file mode 100644 index 00000000..a5215372 --- /dev/null +++ b/Tests/Twig/DataGridExtensionTest.php @@ -0,0 +1,66 @@ +createMock(RouterInterface::class); + $this->extension = new DataGridExtension($router, ''); + } + + public function testGetGridUrl() + { + $baseUrl = 'http://localhost'; + $gridHash = 'my_grid'; + + // Creates grid + $grid = $this->createMock(Grid::class); + $grid->expects($this->any())->method('getRouteUrl')->willReturn($baseUrl); + $grid->expects($this->any())->method('getHash')->willReturn($gridHash); + + $prefix = $baseUrl . '?' . $gridHash; + + // Creates column + $column = $this->createMock(Column::class); + + // Limit + $this->assertEquals($prefix . '[_limit]=', $this->extension->getGridUrl('limit', $grid, $column)); + + // Reset + $this->assertEquals($prefix . '[_reset]=', $this->extension->getGridUrl('reset', $grid, $column)); + + // Page + $this->assertEquals($prefix . '[_page]=2', $this->extension->getGridUrl('page', $grid, 2)); + + // Export + $this->assertEquals($prefix . '[__export_id]=pdf', $this->extension->getGridUrl('export', $grid, 'pdf')); + + // Default order + $column->expects($this->any())->method('getId')->willReturn('foo'); + $this->assertEquals($prefix . '[_order]=foo|asc', $this->extension->getGridUrl('order', $grid, $column)); + + // Order + $column->expects($this->any())->method('isSorted')->willReturn(true); + $column->expects($this->any())->method('getOrder')->willReturn('asc'); + $this->assertEquals($prefix . '[_order]=foo|desc', $this->extension->getGridUrl('order', $grid, $column)); + + // Unknown section + $this->assertNull($this->extension->getGridUrl('', $grid, $column)); + } +} diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php index 0b2e9806..938d3b39 100644 --- a/Tests/bootstrap.php +++ b/Tests/bootstrap.php @@ -1,6 +1,7 @@ annotated = false; $this->parsedClassName = null; } - public function enterNode(\PHPParser_Node $node) { + public function enterNode(\PHPParser_Node $node) + { if ($node instanceof \PHPParser_Node_Stmt_Namespace) { // Base namespace $this->parsedClassName = $node->name->toString(); - } - elseif ($node instanceof \PHPParser_Node_Stmt_UseUse) { + } elseif ($node instanceof \PHPParser_Node_Stmt_UseUse) { // Don't worry about classes that don't import the grid mapper if ('APY_DataGridBundle_Grid_Mapping' == $node->name->toString('_')) { $this->annotated = true; } - } - elseif ($node instanceof \PHPParser_Node_Stmt_Class) { + } elseif ($node instanceof \PHPParser_Node_Stmt_Class) { // Append class name to base namespace $this->parsedClassName .= '\\' . $node->name; } } - public function leaveNode(\PHPParser_Node $node) { } - public function afterTraverse(array $nodes) { } + public function leaveNode(\PHPParser_Node $node) + { + } + public function afterTraverse(array $nodes) + { + } - public function visitFile(\SplFileInfo $file, MessageCatalogue $catalogue) { } + public function visitFile(\SplFileInfo $file, MessageCatalogue $catalogue) + { + } - public function visitPhpFile(\SplFileInfo $file, MessageCatalogue $catalogue, array $ast) { + public function visitPhpFile(\SplFileInfo $file, MessageCatalogue $catalogue, array $ast) + { $this->catalogue = $catalogue; // Traverse document to assemble class name @@ -63,7 +68,7 @@ public function visitPhpFile(\SplFileInfo $file, MessageCatalogue $catalogue, ar // Save messages for title foreach ($metadata->getFields() as $field) { $mappedField = $metadata->getFieldMapping($field); - if ((! isset($mappedField['visible']) || $mappedField['visible']) && isset($mappedField['title'])) { + if ((!isset($mappedField['visible']) || $mappedField['visible']) && isset($mappedField['title'])) { $message = new Message($mappedField['title']); $message->addSource(new FileSource((string) $file)); $catalogue->add($message); @@ -72,5 +77,7 @@ public function visitPhpFile(\SplFileInfo $file, MessageCatalogue $catalogue, ar } } - public function visitTwigFile(\SplFileInfo $file, MessageCatalogue $catalogue, \Twig_Node $node) { } -} \ No newline at end of file + public function visitTwigFile(\SplFileInfo $file, MessageCatalogue $catalogue, \Twig_Node $node) + { + } +} diff --git a/Twig/DataGridExtension.php b/Twig/DataGridExtension.php index e3bb7290..59882932 100644 --- a/Twig/DataGridExtension.php +++ b/Twig/DataGridExtension.php @@ -12,24 +12,33 @@ namespace APY\DataGridBundle\Twig; +use APY\DataGridBundle\Grid\Column\Column; use APY\DataGridBundle\Grid\Grid; -use Pagerfanta\Pagerfanta; use Pagerfanta\Adapter\NullAdapter; +use Pagerfanta\Pagerfanta; use Symfony\Component\Routing\RouterInterface; - -class DataGridExtension extends \Twig_Extension +use Twig\TemplateWrapper; +use Twig\Extension\AbstractExtension; +use Twig\Extension\GlobalsInterface; +use Twig\Environment; +use Twig\TwigFunction; + +/** + * DataGrid Twig Extension. + * + * (c) Abhoryo + * (c) Stanislav Turza + * + * Updated by Nicolas Claverie + */ +class DataGridExtension extends AbstractExtension implements GlobalsInterface { - const DEFAULT_TEMPLATE = 'APYDataGridBundle::blocks.html.twig'; + const DEFAULT_TEMPLATE = '@APYDataGrid/blocks.html.twig'; /** - * @var \Twig_Environment + * @var TemplateWrapper[] */ - protected $environment; - - /** - * @var \Twig_TemplateInterface[] - */ - protected $templates = array(); + protected $templates = []; /** * @var string @@ -37,8 +46,8 @@ class DataGridExtension extends \Twig_Extension protected $theme; /** - * @var RouterInterface - */ + * @var RouterInterface + */ protected $router; /** @@ -49,7 +58,7 @@ class DataGridExtension extends \Twig_Extension /** * @var array */ - protected $params = array(); + protected $params = []; /** * @var array @@ -63,7 +72,7 @@ class DataGridExtension extends \Twig_Extension /** * @param RouterInterface $router - * @param string $defaultTemplate + * @param string $defaultTemplate */ public function __construct($router, $defaultTemplate) { @@ -71,49 +80,29 @@ public function __construct($router, $defaultTemplate) $this->defaultTemplate = $defaultTemplate; } + /** + * @param array $def + */ public function setPagerFanta(array $def) { - $this->pagerFantaDefs=$def; + $this->pagerFantaDefs = $def; } - public function initRuntime(\Twig_Environment $environment) + /** + * @return array + */ + public function getGlobals(): array { - $this->environment = $environment; - - // Avoids the exception "Variable does not exist" with the _self template - $globals = $this->environment->getGlobals(); - - if (!isset($globals['grid'])) { - $this->environment->addGlobal('grid', null); - } - - if (!isset($globals['column'])) { - $this->environment->addGlobal('column', null); - } - - if (!isset($globals['row'])) { - $this->environment->addGlobal('row', null); - } - - if (!isset($globals['value'])) { - $this->environment->addGlobal('value', null); - } - - if (!isset($globals['submitOnChange'])) { - $this->environment->addGlobal('submitOnChange', null); - } - - if (!isset($globals['withjs'])) { - $this->environment->addGlobal('withjs', true); - } - - if (!isset($globals['pagerfanta'])) { - $this->environment->addGlobal('pagerfanta', false); - } - - if (!isset($globals['op'])) { - $this->environment->addGlobal('op', 'eq'); - } + return [ + 'grid' => null, + 'column' => null, + 'row' => null, + 'value' => null, + 'submitOnChange' => null, + 'withjs' => true, + 'pagerfanta' => false, + 'op' => 'eq', + ]; } /** @@ -121,186 +110,250 @@ public function initRuntime(\Twig_Environment $environment) * * @return array An array of functions */ - public function getFunctions() + public function getFunctions(): array { - return array( - 'grid' => new \Twig_Function_Method($this, 'getGrid', array('is_safe' => array('html'))), - 'grid_html' => new \Twig_Function_Method($this, 'getGridHtml', array('is_safe' => array('html'))), - 'grid_url' => new \Twig_Function_Method($this, 'getGridUrl', array('is_safe' => array('html'))), - 'grid_filter' => new \Twig_Function_Method($this, 'getGridFilter', array('is_safe' => array('html'))), - 'grid_column_operator' => new \Twig_Function_Method($this, 'getGridColumnOperator', array('is_safe' => array('html'))), - 'grid_cell' => new \Twig_Function_Method($this, 'getGridCell', array('is_safe' => array('html'))), - 'grid_search' => new \Twig_Function_Method($this, 'getGridSearch', array('is_safe' => array('html'))), - 'grid_pager' => new \Twig_Function_Method($this, 'getGridPager', array('is_safe' => array('html'))), - 'grid_pagerfanta' => new \Twig_Function_Method($this, 'getPagerfanta', array('is_safe' => array('html'))), - // Other methods with only the grid as input and output argument (Twig >= 1.5.0) - 'grid_*' => new \Twig_Function_Method($this, 'getGrid_', array('is_safe' => array('html'))) - ); + return [ + new TwigFunction('grid', [$this, 'getGrid'], [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ]), + new TwigFunction('grid_html', [$this, 'getGridHtml'], [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ]), + new TwigFunction('grid_url', [$this, 'getGridUrl'], [ + 'is_safe' => ['html'], + ]), + new TwigFunction('grid_filter', [$this, 'getGridFilter'], [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ]), + new TwigFunction('grid_column_operator', [$this, 'getGridColumnOperator'], [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ]), + new TwigFunction('grid_cell', [$this, 'getGridCell'], [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ]), + new TwigFunction('grid_search', [$this, 'getGridSearch'], [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ]), + new TwigFunction('grid_pager', [$this, 'getGridPager'], [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ]), + new TwigFunction('grid_pagerfanta', [$this, 'getPagerfanta'], [ + 'is_safe' => ['html'], + ]), + new TwigFunction('grid_*', [$this, 'getGrid_'], [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ]), + ]; } - public function initGrid($grid, $theme = null, $id = '', array $params = array()) + /** + * @param unknown $grid + * @param unknown $theme + * @param string $id + * @param array $params + */ + public function initGrid($grid, $theme = null, $id = '', array $params = []) { $this->theme = $theme; - $this->templates = array(); + $this->templates = []; $this->names[$grid->getHash()] = ($id == '') ? $grid->getId() : $id; $this->params = $params; } /** - * Render grid block + * Render grid block. * - * @param \APY\DataGridBundle\Grid\Grid $grid - * @param string $theme - * @param string $id + * @param Environment $environment + * @param Grid $grid + * @param string $theme + * @param string $id * * @return string */ - public function getGrid($grid, $theme = null, $id = '', array $params = array(), $withjs = true) + public function getGrid(Environment $environment, $grid, $theme = null, $id = '', array $params = [], $withjs = true) { $this->initGrid($grid, $theme, $id, $params); // For export $grid->setTemplate($theme); - return $this->renderBlock('grid', array('grid' => $grid, 'withjs' => $withjs)); + return $this->renderBlock($environment, 'grid', ['grid' => $grid, 'withjs' => $withjs]); } /** - * Render grid block (html only) + * Render grid block (html only). * - * @param \APY\DataGridBundle\Grid\Grid $grid - * @param string $theme - * @param string $id + * @param Environment $environment + * @param Grid $grid + * @param string $theme + * @param string $id * * @return string */ - public function getGridHtml($grid, $theme = null, $id = '', array $params = array()) + public function getGridHtml(Environment $environment, $grid, $theme = null, $id = '', array $params = []) { - return $this->getGrid($grid, $theme, $id, $params, false); + return $this->getGrid($environment, $grid, $theme, $id, $params, false); } - public function getGrid_($name, $grid) + /** + * @param Environment $environment + * @param string $name + * @param unknown $grid + * + * @return string + */ + public function getGrid_(Environment $environment, $name, $grid) { - return $this->renderBlock('grid_' . $name, array('grid' => $grid)); + return $this->renderBlock($environment, 'grid_' . $name, ['grid' => $grid]); } - public function getGridPager($grid) + /** + * @param Environment $environment + * @param unknown $grid + * + * @return string + */ + public function getGridPager(Environment $environment, $grid) { - return $this->renderBlock('grid_pager', array('grid' => $grid, 'pagerfanta' => $this->pagerFantaDefs['enable'])); + return $this->renderBlock($environment, 'grid_pager', ['grid' => $grid, 'pagerfanta' => $this->pagerFantaDefs['enable']]); } /** - * Cell Drawing override + * Cell Drawing override. * - * @param \APY\DataGridBundle\Grid\Column\Column $column - * @param \APY\DataGridBundle\Grid\Row $row - * @param \APY\DataGridBundle\Grid\Grid $grid + * @param Environment $environment + * @param Column $column + * @param \APY\DataGridBundle\Grid\Row $row + * @param Grid $grid * * @return string */ - public function getGridCell($column, $row, $grid) + public function getGridCell(Environment $environment, $column, $row, $grid) { $value = $column->renderCell($row->getField($column->getId()), $row, $this->router); $id = $this->names[$grid->getHash()]; - if (($id != '' && ($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') + if (($id != '' && ($this->hasBlock($environment, $block = 'grid_' . $id . '_column_' . $column->getRenderBlockId() . '_cell') + || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_' . $column->getType() . '_cell') + || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_' . $column->getParentType() . '_cell') + || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_id_' . $column->getRenderBlockId() . '_cell') + || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_type_' . $column->getType() . '_cell') + || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_type_' . $column->getParentType() . '_cell'))) + || $this->hasBlock($environment, $block = 'grid_column_' . $column->getRenderBlockId() . '_cell') + || $this->hasBlock($environment, $block = 'grid_column_' . $column->getType() . '_cell') + || $this->hasBlock($environment, $block = 'grid_column_' . $column->getParentType() . '_cell') + || $this->hasBlock($environment, $block = 'grid_column_id_' . $column->getRenderBlockId() . '_cell') + || $this->hasBlock($environment, $block = 'grid_column_type_' . $column->getType() . '_cell') + || $this->hasBlock($environment, $block = 'grid_column_type_' . $column->getParentType() . '_cell') ) { - return $this->renderBlock($block, array('grid' => $grid, 'column' => $column, 'row' => $row, 'value' => $value)); + return $this->renderBlock($environment, $block, ['grid' => $grid, 'column' => $column, 'row' => $row, 'value' => $value]); } - return $this->renderBlock('grid_column_cell', array('grid' => $grid, 'column' => $column, 'row' => $row, 'value' => $value)); + return $this->renderBlock($environment, 'grid_column_cell', ['grid' => $grid, 'column' => $column, 'row' => $row, 'value' => $value]); } /** - * Filter Drawing override + * Filter Drawing override. * - * @param \APY\DataGridBundle\Grid\Column\Column $column - * @param \APY\DataGridBundle\Grid\Grid $grid + * @param Environment $environment + * @param Column $column + * @param Grid $grid * * @return string */ - public function getGridFilter($column, $grid, $submitOnChange = true) + public function getGridFilter(Environment $environment, $column, $grid, $submitOnChange = true) { $id = $this->names[$grid->getHash()]; - if (($id != '' && ($this->hasBlock($block = 'grid_'.$id.'_column_'.$column->getRenderBlockId().'_filter') - || $this->hasBlock($block = 'grid_'.$id.'_column_id_'.$column->getRenderBlockId().'_filter') - || $this->hasBlock($block = 'grid_'.$id.'_column_type_'.$column->getType().'_filter') - || $this->hasBlock($block = 'grid_'.$id.'_column_type_'.$column->getParentType().'_filter')) - || $this->hasBlock($block = 'grid_'.$id.'_column_filter_type_'.$column->getFilterType())) - || $this->hasBlock($block = 'grid_column_'.$column->getRenderBlockId().'_filter') - || $this->hasBlock($block = 'grid_column_id_'.$column->getRenderBlockId().'_filter') - || $this->hasBlock($block = 'grid_column_type_'.$column->getType().'_filter') - || $this->hasBlock($block = 'grid_column_type_'.$column->getParentType().'_filter') - || $this->hasBlock($block = 'grid_column_filter_type_'.$column->getFilterType()) - ) { - return $this->renderBlock($block, array('grid' => $grid, 'column' => $column, 'submitOnChange' => $submitOnChange && $column->isFilterSubmitOnChange())); + if (($id != '' && ($this->hasBlock($environment, $block = 'grid_' . $id . '_column_' . $column->getRenderBlockId() . '_filter') + || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_id_' . $column->getRenderBlockId() . '_filter') + || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_type_' . $column->getType() . '_filter') + || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_type_' . $column->getParentType() . '_filter')) + || $this->hasBlock($environment, $block = 'grid_' . $id . '_column_filter_type_' . $column->getFilterType())) + || $this->hasBlock($environment, $block = 'grid_column_' . $column->getRenderBlockId() . '_filter') + || $this->hasBlock($environment, $block = 'grid_column_id_' . $column->getRenderBlockId() . '_filter') + || $this->hasBlock($environment, $block = 'grid_column_type_' . $column->getType() . '_filter') + || $this->hasBlock($environment, $block = 'grid_column_type_' . $column->getParentType() . '_filter') + || $this->hasBlock($environment, $block = 'grid_column_filter_type_' . $column->getFilterType()) + ) { + return $this->renderBlock($environment, $block, ['grid' => $grid, 'column' => $column, 'submitOnChange' => $submitOnChange && $column->isFilterSubmitOnChange()]); } return ''; } /** - * Column Operator Drawing override + * Column Operator Drawing override. * - * @param \APY\DataGridBundle\Grid\Column\Column $column - * @param \APY\DataGridBundle\Grid\Grid $grid + * @param Environment $environment + * @param Column $column + * @param Grid $grid * * @return string */ - public function getGridColumnOperator($column, $grid, $operator, $submitOnChange = true) + public function getGridColumnOperator(Environment $environment, $column, $grid, $operator, $submitOnChange = true) { - return $this->renderBlock('grid_column_operator', array('grid' => $grid, 'column' => $column, 'submitOnChange' => $submitOnChange, 'op' => $operator)); + return $this->renderBlock($environment, 'grid_column_operator', ['grid' => $grid, 'column' => $column, 'submitOnChange' => $submitOnChange, 'op' => $operator]); } /** * @param string $section - * @param \APY\DataGridBundle\Grid\Grid $grid - * @param \APY\DataGridBundle\Grid\Column\Column $param + * @param Grid $grid + * @param Column $param + * * @return string */ public function getGridUrl($section, $grid, $param = null) { - $prefix = $grid->getRouteUrl().(strpos($grid->getRouteUrl(), '?') ? '&' : '?').$grid->getHash().'['; + $prefix = $grid->getRouteUrl() . (strpos($grid->getRouteUrl(), '?') ? '&' : '?') . $grid->getHash() . '['; switch ($section) { case 'order': if ($param->isSorted()) { - return $prefix.Grid::REQUEST_QUERY_ORDER.']='.$param->getId().'|'.($param->getOrder() == 'asc' ? 'desc' : 'asc'); + return $prefix . Grid::REQUEST_QUERY_ORDER . ']=' . $param->getId() . '|' . ($param->getOrder() == 'asc' ? 'desc' : 'asc'); } else { - return $prefix.Grid::REQUEST_QUERY_ORDER.']='.$param->getId().'|asc'; + return $prefix . Grid::REQUEST_QUERY_ORDER . ']=' . $param->getId() . '|asc'; } case 'page': - return $prefix.Grid::REQUEST_QUERY_PAGE.']='.$param; + return $prefix . Grid::REQUEST_QUERY_PAGE . ']=' . $param; case 'limit': - return $prefix.Grid::REQUEST_QUERY_LIMIT.']='; + return $prefix . Grid::REQUEST_QUERY_LIMIT . ']='; case 'reset': - return $prefix.Grid::REQUEST_QUERY_RESET.']='; + return $prefix . Grid::REQUEST_QUERY_RESET . ']='; case 'export': - return $prefix.Grid::REQUEST_QUERY_EXPORT.']='.$param; + return $prefix . Grid::REQUEST_QUERY_EXPORT . ']=' . $param; } } - public function getGridSearch($grid, $theme = null, $id = '', array $params = array()) + /** + * @param Environment $environment + * @param unknown $grid + * @param unknown $theme + * @param string $id + * @param array $params + * + * @return string + */ + public function getGridSearch(Environment $environment, $grid, $theme = null, $id = '', array $params = []) { $this->initGrid($grid, $theme, $id, $params); - return $this->renderBlock('grid_search', array('grid' => $grid)); + return $this->renderBlock($environment, 'grid_search', ['grid' => $grid]); } + /** + * @param unknown $grid + */ public function getPagerfanta($grid) { $adapter = new NullAdapter($grid->getTotalCount()); @@ -310,31 +363,30 @@ public function getPagerfanta($grid) $pagerfanta->setCurrentPage($grid->getPage() + 1); $url = $this->getGridUrl('page', $grid, ''); - $routeGenerator = function ($page) use ($url) { - return sprintf('%s%d', $url, $page - 1); - }; + $routeGenerator = fn($page) => sprintf('%s%d', $url, $page - 1); - $view = new $this->pagerFantaDefs['view_class']; + $view = new $this->pagerFantaDefs['view_class'](); $html = $view->render($pagerfanta, $routeGenerator, $this->pagerFantaDefs['options']); return $html; } /** - * Render block - * - * @param string $name - * @param array $parameters + * Render block. * - * @return string + * @param Environment $environment + * @param string $name + * @param array $parameters * * @throws \InvalidArgumentException If the block could not be found + * + * @return string */ - protected function renderBlock($name, $parameters) + protected function renderBlock(Environment $environment, $name, $parameters) { - foreach ($this->getTemplates() as $template) { - if ($template->hasBlock($name)) { - return $template->renderBlock($name, array_merge($this->environment->getGlobals(), $parameters, $this->params)); + foreach ($this->getTemplates($environment) as $template) { + if ($template->hasBlock($name, [])) { + return $template->renderBlock($name, array_merge($environment->getGlobals(), $parameters, $this->params)); } } @@ -342,16 +394,18 @@ protected function renderBlock($name, $parameters) } /** - * Has block + * Has block. * + * @param Environment $environment * @param $name string * - * @return boolean + * @return bool */ - protected function hasBlock($name) + protected function hasBlock(Environment $environment, $name) { - foreach ($this->getTemplates() as $template) { - if ($template->hasBlock($name)) { + foreach ($this->getTemplates($environment) as $template) { + /** @var $template Twig_Template */ + if ($template->hasBlock($name, [])) { return true; } } @@ -360,22 +414,24 @@ protected function hasBlock($name) } /** - * Template Loader + * Template Loader. * - * @return \Twig_Template[] + * @param Environment $environment * * @throws \Exception + * + * @return TemplateWrapper[] */ - protected function getTemplates() + protected function getTemplates(Environment $environment) { if (empty($this->templates)) { - if ($this->theme instanceof \Twig_Template) { + if ($this->theme instanceof TemplateWrapper) { $this->templates[] = $this->theme; - $this->templates[] = $this->environment->loadTemplate($this->defaultTemplate); + $this->templates[] = $environment->load($this->defaultTemplate); } elseif (is_string($this->theme)) { - $this->templates = $this->getTemplatesFromString($this->theme); + $this->templates = $this->getTemplatesFromString($environment, $this->theme); } elseif ($this->theme === null) { - $this->templates = $this->getTemplatesFromString($this->defaultTemplate); + $this->templates = $this->getTemplatesFromString($environment, $this->defaultTemplate); } else { throw new \Exception('Unable to load template'); } @@ -384,21 +440,19 @@ protected function getTemplates() return $this->templates; } - protected function getTemplatesFromString($theme) + /** + * @param Environment $environment + * @param string $theme + * + * @return array + */ + protected function getTemplatesFromString(Environment $environment, $theme) { - $this->templates = array(); + $this->templates = []; - $template = $this->environment->loadTemplate($theme); - while ($template != null) { - $this->templates[] = $template; - $template = $template->getParent(array()); - } + $template = $environment->load($theme); + $this->templates[] = $template; return $this->templates; } - - public function getName() - { - return 'datagrid_twig_extension'; - } } diff --git a/composer.json b/composer.json index 680025a8..bcb76c62 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { "name": "apy/datagrid-bundle", - "description": "Symfony2 Datagrid Bundle", + "description": "Symfony Datagrid Bundle", "keywords": ["Symfony", "datagrid"], - "homepage": "https://github.com/Abhoryo/APYDataGridBundle", + "homepage": "https://github.com/apy/APYDataGridBundle", "type": "symfony-bundle", "license": "MIT", "authors": [ @@ -17,24 +17,56 @@ { "name": "Evan Owens", "email": "eaowens@gmail.com" + }, + { + "name": "Nicolas Potier", + "email": "contact@acseo.fr" } ], "require": { - "php": ">=5.3.2", - "symfony/symfony": ">=2.0.0", - "twig/twig": ">=1.5.0" + "php": "^7.4 || ^8.0", + "symfony/form": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/config": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/http-foundation": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/http-kernel": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/options-resolver": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/security-core": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/serializer": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "twig/twig": "^2.14 || ^3.0" + }, + "require-dev": { + "symfony/browser-kit": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/expression-language": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/framework-bundle": "^4.3 || ^5.0 || ^6.0 || ^7.0", + "symfony/security-bundle": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "symfony/twig-bundle": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "phpunit/phpunit": "^9.5", + "friendsofphp/php-cs-fixer": "^3.0", + "php-coveralls/php-coveralls": "^2.0", + "doctrine/orm": "~2.10,>=2.10.0", + "doctrine/mongodb-odm": "^2.2", + "rector/rector": "^0.12.13", + "dg/bypass-finals": "^1.3", + "doctrine/doctrine-bundle": "^2.5" }, "suggest": { "ext-intl": "Translate the grid", "ext-mbstring": "Convert your data with the right charset", - "PHPExcel": "Export the grid (Excel, HTML or PDF)" + "PHPExcel": "Export the grid (Excel, HTML or PDF)", + "doctrine/orm": "If you want to use Entity as source, please require doctrine/orm", + "doctrine/mongodb-odm": "If you want to use Document as source, please require doctrine/mongodb-odm", + "jms/translation-bundle": "If you want to use translations" }, "autoload": { "psr-4": { "APY\\DataGridBundle\\": "" } }, "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "6.x-dev" } + }, + "provide": { + "ext-mongo": "1.5" } -} \ No newline at end of file +} diff --git a/php_cs.dist b/php_cs.dist new file mode 100644 index 00000000..73e99907 --- /dev/null +++ b/php_cs.dist @@ -0,0 +1,91 @@ +in([__DIR__]) + ->exclude('vendor') + ->name('*.php'); + +return Symfony\CS\Config\Config::create() + ->finder($finder) + ->setUsingCache(true) + ->fixers([ + // 'psr0', // [PSR-0] Classes must be in a path that matches their namespace, be at least one namespace deep, and the class name should match the file name. + + 'encoding', // [PSR-1] PHP code MUST use only UTF-8 without BOM (remove BOM). + 'short_tag', // [PSR-1] PHP code must use the long should not be arounded by multi-line whitespaces. + 'duplicate_semicolon', // [symfony] Remove duplicated semicolons. + 'empty_return', // [symfony] A return statement wishing to return nothing should be simply "return". + 'extra_empty_lines', // [symfony] Removes extra empty lines + 'function_typehint_space', + 'include', // [symfony] Include and file path should be divided with a single space. File path should not be placed under brackets. + 'join_function', // [symfony] Implode function should be used instead of join function. + 'list_commas', // [symfony] Remove trailing commas in list function calls. + 'multiline_array_trailing_comma', // [symfony] PHP multi-line arrays should have a trailing comma. + 'namespace_no_leading_whitespace', // [symfony] The namespace declaration line shouldn't contain leading whitespace. + // 'new_with_braces', // [symfony] All instances created with new keyword must be followed by braces. + 'no_blank_lines_after_class_opening', // [symfony] There should be no empty lines after class opening brace. + 'no_empty_lines_after_phpdocs', // [symfony] There should not be blank lines between docblock and the documented element. + 'object_operator', // [symfony] There should not be space before or after object T_OBJECT_OPERATOR. + 'operators_spaces', // [symfony] Binary operators should be arounded by at least one space. + 'phpdoc_indent', // [symfony] Docblocks should have the same indentation as the documented subject. + 'phpdoc_inline_tag', + 'phpdoc_no_access', // [symfony] @access annotations should be omitted from phpdocs. + 'phpdoc_no_empty_return', // [symfony] @return void and @return null annotations should be omitted from phpdocs. + 'phpdoc_no_package', // [symfony] @package and @subpackage annotations should be omitted from phpdocs. + 'phpdoc_params', // [symfony] All items of the @param, @throws, @return, @var, and @type phpdoc tags must be aligned vertically. + 'phpdoc_scalar', // [symfony] Scalar types should always be written in the same form. "int", not "integer"; "bool", not "boolean"; "float", not "real" or "double". + 'phpdoc_separation', // [symfony] Annotations in phpdocs should be grouped together so that annotations of the same type immediately follow each other, and annotations of a different type are separated by a single blank line. + 'phpdoc_short_description', // [symfony] Phpdocs short descriptions should end in either a full stop, exclamation mark, or question mark. + 'phpdoc_to_comment', // [symfony] Docblocks should only be used on structural elements. + 'phpdoc_trim', // [symfony] Phpdocs should start and end with content, excluding the very first and last line of the docblocks. + 'phpdoc_type_to_var', // [symfony] @type should always be written as @var. + 'phpdoc_var_without_name', // [symfony] @var and @type annotations should not contain the variable name. + 'pre_increment', // [symfony] Pre incrementation/decrementation should be used if possible. + 'remove_leading_slash_use', // [symfony] Remove leading slashes in use clauses. + 'remove_lines_between_uses', // [symfony] Removes line breaks between use statements. + 'return', // [symfony] An empty line feed should precede a return statement. + 'self_accessor', // [symfony] Inside a classy element "self" should be preferred to the class name itself. + //'single_array_no_trailing_comma', // [symfony] PHP single-line arrays should not have trailing comma. + 'single_blank_line_before_namespace', // [symfony] There should be exactly one blank line before a namespace declaration. + 'single_quote', // [symfony] Convert double quotes to single quotes for simple strings. + 'spaces_before_semicolon', // [symfony] Single-line whitespace before closing semicolon are prohibited. + 'spaces_cast', // [symfony] A single space should be between cast and variable. + 'standardize_not_equal', // [symfony] Replace all <> with !=. + 'ternary_spaces', // [symfony] Standardize spaces around ternary operator. + 'trim_array_spaces', // [symfony] Arrays should be formatted like function/method arguments, without leading or trailing single line space. + // 'unalign_double_arrow', // [symfony] Unalign double arrow symbols. + 'unalign_equals', // [symfony] Unalign equals symbols. + 'unneeded_control_parentheses', + 'unary_operators_spaces', // [symfony] Unary operators should be placed adjacent to their operands. + 'unused_use', // [symfony] Unused use statements must be removed. + 'whitespacy_lines', // [symfony] Remove trailing whitespace at the end of blank lines. + + 'concat_with_spaces', + 'align_double_arrow', + 'multiline_spaces_before_semicolon', + 'ordered_use', + 'phpdoc_order', + 'short_array_syntax', + ]); diff --git a/phpunit.xml.dist b/phpunit.xml.dist index c3c62ba8..0d6292fc 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,6 +1,13 @@ - + ./Tests @@ -12,7 +19,14 @@ ./Resources ./Tests + ./vendor + + + + + + \ No newline at end of file diff --git a/rector.php b/rector.php new file mode 100644 index 00000000..9643e3f3 --- /dev/null +++ b/rector.php @@ -0,0 +1,25 @@ +paths([ + //__DIR__ . '/src', + __DIR__ . '/Tests' + ]); + + // register a single rule + $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); + + // define sets of rules + $rectorConfig->sets([ + LevelSetList::UP_TO_PHP_74, + LevelSetList::UP_TO_PHP_80, + PHPUnitSetList::PHPUNIT_91 + ]); +};