Completed
Push — master ( 63bbad...0aea12 )
by Pavel
02:57
created

DataGrid::setColumnsHideable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
c 3
b 1
f 1
dl 0
loc 6
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid;
10
11
use Nette;
12
use Ublaboo\DataGrid\Utils\ArraysHelper;
13
use Nette\Application\UI\Form;
14
15
class DataGrid extends Nette\Application\UI\Control
16
{
17
18
	/**
19
	 * @var callable[]
20
	 */
21
	public $onRedraw;
22
23
	/**
24
	 * @var string
25
	 * @todo Tell about this on github
26
	 */
27
	public static $icon_prefix = 'fa fa-';
28
29
	/**
30
	 * When set to TRUE, datagrid throws an exception
31
	 * 	when tring to get related entity within join and entity does not exist
32
	 * @var bool
33
	 */
34
	public $strict_entity_property = FALSE;
35
36
	/**
37
	 * @var int
38
	 * @persistent
39
	 */
40
	public $page = 1;
41
42
	/**
43
	 * @var int
44
	 * @persistent
45
	 */
46
	public $per_page;
47
48
	/**
49
	 * @var array
50
	 * @persistent
51
	 */
52
	public $sort = [];
53
54
	/**
55
	 * @var array
56
	 * @persistent
57
	 */
58
	public $filter = [];
59
60
	/**
61
	 * @var Callable[]
62
	 */
63
	public $onRender = [];
64
65
	/**
66
	 * @var Callable[]
67
	 */
68
	protected $rowCallback;
69
70
	/**
71
	 * @var array
72
	 */
73
	protected $items_per_page_list = [10, 20, 50];
74
75
	/**
76
	 * @var string
77
	 */
78
	protected $template_file;
79
80
	/**
81
	 * @var Column\IColumn[]
82
	 */
83
	protected $columns = [];
84
85
	/**
86
	 * @var Column\Action[]
87
	 */
88
	protected $actions = [];
89
90
	/**
91
	 * @var GroupAction\GroupActionCollection
92
	 */
93
	protected $group_action_collection;
94
95
	/**
96
	 * @var Filter\Filter[]
97
	 */
98
	protected $filters = [];
99
100
	/**
101
	 * @var Export\Export[]
102
	 */
103
	protected $exports = [];
104
105
	/**
106
	 * @var DataModel
107
	 */
108
	protected $dataModel;
109
110
	/**
111
	 * @var DataFilter
112
	 */
113
	protected $dataFilter;
114
115
	/**
116
	 * @var string
117
	 */
118
	protected $primary_key = 'id';
119
120
	/**
121
	 * @var bool
122
	 */
123
	protected $do_paginate = TRUE;
124
125
	/**
126
	 * @var bool
127
	 */
128
	protected $csv_export = TRUE;
129
130
	/**
131
	 * @var bool
132
	 */
133
	protected $csv_export_filtered = TRUE;
134
135
	/**
136
	 * @var bool
137
	 */
138
	protected $sortable = FALSE;
139
	
140
	/**
141
	 * @var string
142
	 */
143
	protected $sortable_handler = 'sort!';
144
145
	/**
146
	 * @var string
147
	 */
148
	protected $original_template;
149
150
	/**
151
	 * @var array
152
	 */
153
	protected $redraw_item;
154
155
	/**
156
	 * @var mixed
157
	 */
158
	protected $translator;
159
160
	/**
161
	 * @var bool
162
	 */
163
	protected $force_filter_active;
164
165
	/**
166
	 * @var callable
167
	 */
168
	protected $tree_view_children_callback;
169
170
	/**
171
	 * @var string
172
	 */
173
	protected $tree_view_has_children_column;
174
175
	/**
176
	 * @var bool
177
	 */
178
	protected $outer_filter_rendering = FALSE;
179
180
	/**
181
	 * @var array
182
	 */
183
	protected $columns_export_order = [];
184
185
	/**
186
	 * @var bool
187
	 */
188
	private $remember_state = TRUE;
189
190
	/**
191
	 * @var bool
192
	 */
193
	private $refresh_url = TRUE;
194
195
	/**
196
	 * @var Nette\Http\SessionSection
197
	 */
198
	private $grid_session;
199
200
	/**
201
	 * @var array
202
	 */
203
	private $items_detail = [];
204
205
	/**
206
	 * @var array
207
	 */
208
	private $row_conditions = [
209
		'group_action' => FALSE,
210
		'action' => []
211
	];
212
213
	/**
214
	 * @var bool
215
	 */
216
	protected $can_hide_columns = FALSE;
217
218
219
	/**
220
	 * @param Nette\ComponentModel\IContainer|NULL $parent
221
	 * @param string                               $name
222
	 */
223
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
224
	{
225
		parent::__construct($parent, $name);
226
227
		$this->monitor('Nette\Application\UI\Presenter');
228
	}
229
230
231
	/**
232
	 * {inheritDoc}
233
	 * @return void
234
	 */
235
	public function attached($presenter)
236
	{
237
		parent::attached($presenter);
238
239
		if ($presenter instanceof Nette\Application\UI\Presenter) {
240
			/**
241
			 * Get session
242
			 */
243
			$this->grid_session = $this->getPresenter()->getSession($this->getSessionSectionName());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getSession() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter, Nette\Forms\Controls\CsrfProtection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
244
245
			/**
246
			 * Try to find previous filters/pagination/sort in session
247
			 */
248
			$this->findSessionFilters();
249
		}
250
	}
251
252
253
	/********************************************************************************
254
	 *                                  RENDERING                                   *
255
	 ********************************************************************************/
256
257
258
	/**
259
	 * Render template
260
	 * @return void
261
	 */
262
	public function render()
263
	{
264
		/**
265
		 * Check whether datagrid has set some columns, initiated data source, etc
266
		 */
267
		if (!($this->dataModel instanceof DataModel)) {
268
			throw new DataGridException('You have to set a data source first.');
269
		}
270
271
		if (empty($this->columns)) {
272
			throw new DataGridException('You have to add at least one column.');
273
		}
274
275
		$this->template->setTranslator($this->getTranslator());
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
276
277
		/**
278
		 * Invoke some possible events
279
		 */
280
		$this->onRender($this);
0 ignored issues
show
Bug introduced by
The method onRender() does not exist on Ublaboo\DataGrid\DataGrid. Did you maybe mean render()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
281
282
		/**
283
		 * Prepare data for rendering (datagrid may render just one item)
284
		 */
285
		$rows = [];
286
287
		if (!empty($this->redraw_item)) {
288
			$items = $this->dataModel->filterRow($this->redraw_item);
289
		} else {
290
			$items = Nette\Utils\Callback::invokeArgs(
291
				[$this->dataModel, 'filterData'],
292
				[
293
					$this->getPaginator(),
294
					$this->sort,
295
					$this->assableFilters()
296
				]
297
			);
298
		}
299
300
		$callback = $this->rowCallback ?: NULL;
301
302
		foreach ($items as $item) {
303
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
304
305
			if ($callback) {
306
				$callback($item, $row->getControl());
307
			}
308
		}
309
310
		if ($this->isTreeView()) {
311
			$this->template->tree_view_has_children_column = $this->tree_view_has_children_column;
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing tree_view_has_children_column on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
312
		}
313
314
		$this->template->rows = $rows;
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing rows on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
315
316
		$this->template->columns = $this->columns;
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing columns on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
317
		$this->template->actions = $this->actions;
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing actions on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
318
		$this->template->exports = $this->exports;
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing exports on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
319
		$this->template->filters = $this->filters;
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing filters on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
320
321
		$this->template->filter_active = $this->isFilterActive();
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing filter_active on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
322
		$this->template->original_template = $this->getOriginalTemplateFile();
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing original_template on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
323
		$this->template->icon_prefix = static::$icon_prefix;
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing icon_prefix on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
324
		$this->template->items_detail = $this->items_detail;
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing items_detail on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
325
326
		/**
327
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
328
		 */
329
		$this->template->filter = $this['filter'];
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing filter on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
330
331
		/**
332
		 * Set template file and render it
333
		 */
334
		$this->template->setFile($this->getTemplateFile())->render();
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
The method render cannot be called on $this->template->setFile...his->getTemplateFile()) (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
335
	}
336
337
338
	/********************************************************************************
339
	 *                                 ROW CALLBACK                                 *
340
	 ********************************************************************************/
341
342
343
	/**
344
	 * Each row can be modified with user callback
345
	 * @param  callable  $callback
346
	 * @return static
347
	 */
348
	public function setRowCallback(callable $callback)
349
	{
350
		$this->rowCallback = $callback;
0 ignored issues
show
Documentation Bug introduced by
It seems like $callback of type callable is incompatible with the declared type array<integer,callable> of property $rowCallback.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
351
352
		return $this;
353
	}
354
355
356
	/********************************************************************************
357
	 *                                 DATA SOURCE                                  *
358
	 ********************************************************************************/
359
360
361
	/**
362
	 * By default ID, you can change that
363
	 * @param string $primary_key
364
	 */
365
	public function setPrimaryKey($primary_key)
366
	{
367
		$this->primary_key = $primary_key;
368
369
		return $this;
370
	}
371
372
373
	/**
374
	 * Set Grid data source
375
	 * @param DataSource\IDataSource|array|\DibiFluent $source
376
	 * @return DataGrid
377
	 */
378
	public function setDataSource($source)
379
	{
380
		if ($source instanceof DataSource\IDataSource) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
381
			// $source is ready for interact
382
383
		} else if (is_array($source)) {
384
			$data_source = new DataSource\ArrayDataSource($source);
385
386
		} else if ($source instanceof \DibiFluent) {
387
			$driver = $source->getConnection()->getDriver();
388
389
			if ($driver instanceof \DibiOdbcDriver) {
390
				$data_source = new DataSource\DibiFluentMssqlDataSource($source, $this->primary_key);
391
392
			} else if ($driver instanceof \DibiMsSqlDriver) {
393
				$data_source = new DataSource\DibiFluentMssqlDataSource($source, $this->primary_key);
394
395
			} else {
396
				$data_source = new DataSource\DibiFluentDataSource($source, $this->primary_key);
397
			}
398
399
		} else if ($source instanceof Nette\Database\Table\Selection) {
0 ignored issues
show
Bug introduced by
The class Nette\Database\Table\Selection does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
400
			$data_source = new DataSource\NetteDatabaseTableDataSource($source, $this->primary_key);
401
402
		} else if ($source instanceof \Kdyby\Doctrine\QueryBuilder) {
0 ignored issues
show
Bug introduced by
The class Kdyby\Doctrine\QueryBuilder does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
403
			$data_source = new DataSource\DoctrineDataSource($source, $this->primary_key);
404
405
		} else {
406
			$data_source_class = $source ? get_class($source) : 'NULL';
407
			throw new DataGridException("DataGrid can not take [$data_source_class] as data source.");
408
		}
409
410
		$this->dataModel = new DataModel($data_source);
0 ignored issues
show
Bug introduced by
The variable $data_source does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
411
412
		return $this;
413
	}
414
415
416
	/********************************************************************************
417
	 *                                  TEMPLATING                                  *
418
	 ********************************************************************************/
419
420
421
	/**
422
	 * Set custom template file to render
423
	 * @param string $template_file
424
	 */
425
	public function setTemplateFile($template_file)
426
	{
427
		$this->template_file = $template_file;
428
429
		return $this;
430
	}
431
432
433
	/**
434
	 * Get DataGrid template file
435
	 * @return string
436
	 */
437
	public function getTemplateFile()
438
	{
439
		return $this->template_file ?: $this->getOriginalTemplateFile();
440
	}
441
442
443
	/**
444
	 * Get DataGrid original template file
445
	 * @return string
446
	 */
447
	public function getOriginalTemplateFile()
448
	{
449
		return __DIR__.'/templates/datagrid.latte';
450
	}
451
452
453
	/********************************************************************************
454
	 *                                   SORTING                                    *
455
	 ********************************************************************************/
456
457
458
	/**
459
	 * Set default sorting
460
	 * @param aray $sort
461
	 */
462
	public function setDefaultSort($sort)
463
	{
464
		if (empty($this->sort)) {
465
			$this->sort = (array) $sort;
466
467
			$this->saveSessionData('_grid_sort', $this->sort);
468
		}
469
	}
470
471
472
	/**
473
	 * Set grido to be sortable
474
	 * @param bool $sortable
475
	 */
476
	public function setSortable($sortable = TRUE)
477
	{
478
		if ($this->getItemsDetail()) {
479
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
480
		}
481
482
		$this->sortable = (bool) $sortable;
483
484
		return $this;
485
	}
486
487
488
	/**
489
	 * Set sortable handle
490
	 * @param string $handle
0 ignored issues
show
Documentation introduced by
There is no parameter named $handle. Did you maybe mean $handler?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
491
	 */
492
	public function setSortableHandler($handler = 'sort!')
493
	{
494
		$this->sortable_handler = (string) $handler;
495
496
		return $this;
497
	}
498
499
500
	/**
501
	 * Tell whether DataGrid is sortable
502
	 * @return bool
503
	 */
504
	public function isSortable()
505
	{
506
		return $this->sortable;
507
	}
508
509
	/**
510
	 * Return sortable handle name
511
	 * @return string
512
	 */
513
	public function getSortableHandler()
514
	{
515
		return $this->sortable_handler;
516
	}
517
518
519
	/********************************************************************************
520
	 *                                  TREE VIEW                                   *
521
	 ********************************************************************************/
522
523
524
	/**
525
	 * Is tree view set?
526
	 * @return boolean
527
	 */
528
	public function isTreeView()
529
	{
530
		return (bool) $this->tree_view_children_callback;
531
	}
532
533
534
	/**
535
	 * Setting tree view
536
	 * @param callable $get_children_callback
537
	 * @param string   $tree_view_has_children_column
538
	 */
539
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
540
	{
541
		if (!is_callable($get_children_callback)) {
542
			throw new DataGridException(
543
				'Parameters to method DataGrid::setTreeView must be of type callable'
544
			);
545
		}
546
547
		$this->tree_view_children_callback = $get_children_callback;
548
		$this->tree_view_has_children_column = $tree_view_has_children_column;
549
550
		/**
551
		 * TUrn off pagination
552
		 */
553
		$this->setPagination(NULL);
0 ignored issues
show
Documentation introduced by
NULL is of type null, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
554
555
		/**
556
		 * Set tree view template file
557
		 */
558
		if (!$this->template_file) {
559
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
560
		}
561
562
		return $this;
563
	}
564
565
566
	/********************************************************************************
567
	 *                                    COLUMNS                                   *
568
	 ********************************************************************************/
569
570
571
	/**
572
	 * Add text column with no other formating
573
	 * @param  string      $key
574
	 * @param  string      $name
575
	 * @param  string|null $column
576
	 * @return Column\Column
577
	 */
578
	public function addColumnText($key, $name, $column = NULL)
579
	{
580
		$this->addColumnCheck($key);
581
		$column = $column ?: $key;
582
583
		return $this->columns[$key] = new Column\ColumnText($column, $name);
584
	}
585
586
587
	/**
588
	 * Add column with link
589
	 * @param  string      $key
590
	 * @param  string      $name
591
	 * @param  string|null $column
592
	 * @return Column\Column
593
	 */
594
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
595
	{
596
		$this->addColumnCheck($key);
597
		$column = $column ?: $key;
598
		$href = $href ?: $key;
599
600
		if (NULL === $params) {
601
			$params = [$this->primary_key];
602
		}
603
604
		return $this->columns[$key] = new Column\ColumnLink($this, $column, $name, $href, $params);
605
	}
606
607
608
	/**
609
	 * Add column with possible number formating
610
	 * @param  string      $key
611
	 * @param  string      $name
612
	 * @param  string|null $column
613
	 * @return Column\Column
614
	 */
615
	public function addColumnNumber($key, $name, $column = NULL)
616
	{
617
		$this->addColumnCheck($key);
618
		$column = $column ?: $key;
619
620
		return $this->columns[$key] = new Column\ColumnNumber($column, $name);
621
	}
622
623
624
	/**
625
	 * Add column with date formating
626
	 * @param  string      $key
627
	 * @param  string      $name
628
	 * @param  string|null $column
629
	 * @return Column\Column
630
	 */
631
	public function addColumnDateTime($key, $name, $column = NULL)
632
	{
633
		$this->addColumnCheck($key);
634
		$column = $column ?: $key;
635
636
		return $this->columns[$key] = new Column\ColumnDateTime($column, $name);
637
	}
638
639
640
	/**
641
	 * Return existing column
642
	 * @param  string $key
643
	 * @return Column\Column
644
	 * @throws DataGridException
645
	 */
646
	public function getColumn($key)
647
	{
648
		if (!isset($this->columns[$key])) {
649
			throw new DataGridException("There is no column at key [$key] defined.");
650
		}
651
652
		return $this->columns[$key];
653
	}
654
655
656
	/**
657
	 * Remove column
658
	 * @param string $key
659
	 * @return void
660
	 */
661
	public function removeColumn($key)
662
	{
663
		unset($this->columns[$key]);
664
	}
665
666
667
	/**
668
	 * Check whether given key already exists in $this->columns
669
	 * @param  string $key
670
	 * @throws DataGridException
671
	 */
672
	protected function addColumnCheck($key)
673
	{
674
		if (isset($this->columns[$key])) {
675
			throw new DataGridException("There is already column at key [$key] defined.");
676
		}
677
	}
678
679
680
	/********************************************************************************
681
	 *                                    ACTIONS                                   *
682
	 ********************************************************************************/
683
684
685
	/**
686
	 * Create action
687
	 * @param string     $key
688
	 * @param string     $name
689
	 * @param string     $href
690
	 * @param array|null $params
691
	 */
692
	public function addAction($key, $name = '', $href = NULL, array $params = NULL)
693
	{
694
		$this->addActionCheck($key);
695
		$href = $href ?: $key;
696
697
		if (NULL === $params) {
698
			$params = [$this->primary_key];
699
		}
700
701
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
702
	}
703
704
705
	/**
706
	 * Get existing action
707
	 * @param  string       $key
708
	 * @return Column\Action
709
	 * @throws DataGridException
710
	 */
711
	public function getAction($key)
712
	{
713
		if (!isset($this->actions[$key])) {
714
			throw new DataGridException("There is no action at key [$key] defined.");
715
		}
716
717
		return $this->actions[$key];
718
	}
719
720
721
	/**
722
	 * Remove action
723
	 * @param string $key
724
	 * @return void
725
	 */
726
	public function removeAction($key)
727
	{
728
		unset($this->actions[$key]);
729
	}
730
731
732
	/**
733
	 * Check whether given key already exists in $this->filters
734
	 * @param  string $key
735
	 * @throws DataGridException
736
	 */
737
	protected function addActionCheck($key)
738
	{
739
		if (isset($this->actions[$key])) {
740
			throw new DataGridException("There is already action at key [$key] defined.");
741
		}
742
	}
743
744
745
	/********************************************************************************
746
	 *                                    FILTERS                                   *
747
	 ********************************************************************************/
748
749
750
	/**
751
	 * Add filter fot text search
752
	 * @param string       $key
753
	 * @param string       $name
754
	 * @param array|string $columns
755
	 * @throws DataGridException
756
	 */
757
	public function addFilterText($key, $name, $columns = NULL)
758
	{
759
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
760
761
		if (!is_array($columns)) {
762
			throw new DataGridException("Filter Text can except only array or string.");
763
		}
764
765
		$this->addFilterCheck($key);
766
767
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
0 ignored issues
show
Documentation introduced by
$columns is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
768
	}
769
770
771
	/**
772
	 * Add select box filter
773
	 * @param string $key
774
	 * @param string $name
775
	 * @param array  $options
776
	 * @param string $column
777
	 * @throws DataGridException
778
	 */
779 View Code Duplication
	public function addFilterSelect($key, $name, $options, $column = NULL)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
780
	{
781
		$column = $column ?: $key;
782
783
		if (!is_string($column)) {
784
			throw new DataGridException("Filter Select can only filter through one column.");
785
		}
786
787
		$this->addFilterCheck($key);
788
789
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
790
	}
791
792
793
	/**
794
	 * Add datepicker filter
795
	 * @param string $key
796
	 * @param string $name
797
	 * @param string $column
798
	 * @throws DataGridException
799
	 */
800
	public function addFilterDate($key, $name, $column = NULL)
801
	{
802
		$column = $column ?: $key;
803
804
		if (!is_string($column)) {
805
			throw new DataGridException("FilterDate can only filter through one column.");
806
		}
807
808
		$this->addFilterCheck($key);
809
810
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
811
	}
812
813
814
	/**
815
	 * Add range filter (from - to)
816
	 * @param string $key
817
	 * @param string $name
818
	 * @param string $column
819
	 * @throws DataGridException
820
	 */
821 View Code Duplication
	public function addFilterRange($key, $name, $column = NULL, $name_second = '-')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
822
	{
823
		$column = $column ?: $key;
824
825
		if (!is_string($column)) {
826
			throw new DataGridException("FilterRange can only filter through one column.");
827
		}
828
829
		$this->addFilterCheck($key);
830
831
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
832
	}
833
834
835
	/**
836
	 * Add datepicker filter (from - to)
837
	 * @param string $key
838
	 * @param string $name
839
	 * @param string $column
840
	 * @throws DataGridException
841
	 */
842 View Code Duplication
	public function addFilterDateRange($key, $name, $column = NULL, $name_second = '-')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
843
	{
844
		$column = $column ?: $key;
845
846
		if (!is_string($column)) {
847
			throw new DataGridException("FilterDateRange can only filter through one column.");
848
		}
849
850
		$this->addFilterCheck($key);
851
852
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
853
	}
854
855
856
	/**
857
	 * Check whether given key already exists in $this->filters
858
	 * @param  string $key
859
	 * @throws DataGridException
860
	 */
861
	protected function addFilterCheck($key)
862
	{
863
		if (isset($this->filters[$key])) {
864
			throw new DataGridException("There is already action at key [$key] defined.");
865
		}
866
	}
867
868
869
	/**
870
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
871
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
872
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
873
	 */
874
	public function assableFilters()
875
	{
876
		foreach ($this->filter as $key => $value) {
877
			if (!isset($this->filters[$key])) {
878
				$this->deleteSesssionData($key);
879
880
				continue;
881
			}
882
883
			if (is_array($value) || $value instanceof \Traversable) {
884
				if (!ArraysHelper::testEmpty($value)) {
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type object<Traversable>; however, Ublaboo\DataGrid\Utils\ArraysHelper::testEmpty() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
885
					$this->filters[$key]->setValue($value);
886
				}
887
			} else {
888
				if ($value !== '' && $value !== NULL) {
889
					$this->filters[$key]->setValue($value);
890
				}
891
			}
892
		}
893
894
		foreach ($this->columns as $column) {
895
			if (isset($this->sort[$column->getColumnName()])) {
896
				$column->setSort($this->sort);
897
			}
898
		}
899
900
		return $this->filters;
901
	}
902
903
904
	/**
905
	 * Remove filter
906
	 * @param string $key
907
	 * @return void
908
	 */
909
	public function removeFilter($key)
910
	{
911
		unset($this->filters[$key]);
912
	}
913
914
915
	/********************************************************************************
916
	 *                                  FILTERING                                   *
917
	 ********************************************************************************/
918
919
920
	/**
921
	 * Is filter active?
922
	 * @return boolean
923
	 */
924
	public function isFilterActive()
925
	{
926
		$is_filter = ArraysHelper::testTruthy($this->filter);
927
928
		return ($is_filter) || $this->force_filter_active;
929
	}
930
931
932
	/**
933
	 * Tell that filter is active from whatever reasons
934
	 * return self
935
	 */
936
	public function setFilterActive()
937
	{
938
		$this->force_filter_active = TRUE;
939
940
		return $this;
941
	}
942
943
944
	/**
945
	 * If we want to sent some initial filter
946
	 * @param array $filter
947
	 */
948
	public function setFilter(array $filter)
949
	{
950
		$this->filter = $filter;
951
952
		return $this;
953
	}
954
955
956
	/**
957
	 * FilterAndGroupAction form factory
958
	 * @return Form
959
	 */
960
	public function createComponentFilter()
961
	{
962
		$form = new Form($this, 'filter');
963
964
		$form->setMethod('get');
965
966
		/**
967
		 * Filter part
968
		 */
969
		$filter_container = $form->addContainer('filter');
970
971
		foreach ($this->filters as $filter) {
972
			$filter->addToFormContainer($filter_container, $filter_container);
0 ignored issues
show
Documentation Bug introduced by
The method addToFormContainer does not exist on object<Ublaboo\DataGrid\Filter\Filter>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
973
		}
974
975
		/**
976
		 * Group action part
977
		 */
978
		$group_action_container = $form->addContainer('group_action');
979
980
		if ($this->hasGroupActions()) {
981
			$this->getGroupActionCollection()->addToFormContainer($group_action_container, $form, $this->getTranslator());
982
		}
983
984
		$form->setDefaults(['filter' => $this->filter]);
985
986
		$form->onSubmit[] = [$this, 'filterSucceeded'];
987
988
		return $form;
989
	}
990
991
992
	/**
993
	 * Set $this->filter values after filter form submitted
994
	 * @param  Form $form
995
	 * @return void
996
	 */
997
	public function filterSucceeded(Form $form)
998
	{
999
		$values = $form->getValues();
1000
1001
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1002
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1003
				return;
1004
			}
1005
		}
1006
1007
		$values = $values['filter'];
1008
1009
		foreach ($values as $key => $value) {
1010
			/**
1011
			 * Session stuff
1012
			 */
1013
			$this->saveSessionData($key, $value);
1014
1015
			/**
1016
			 * Other stuff
1017
			 */
1018
			$this->filter[$key] = $value;
1019
		}
1020
1021
		$this->reload();
1022
	}
1023
1024
1025
	/**
1026
	 * Should be datagrid filters rendered separately?
1027
	 * @param boolean $out
1028
	 */
1029
	public function setOuterFilterRendering($out = TRUE)
1030
	{
1031
		$this->outer_filter_rendering = (bool) $out;
1032
1033
		return $this;
1034
	}
1035
1036
1037
	/**
1038
	 * Are datagrid filters rendered separately?
1039
	 * @return boolean
1040
	 */
1041
	public function hasOuterFilterRendering()
1042
	{
1043
		return $this->outer_filter_rendering;
1044
	}
1045
1046
1047
	/**
1048
	 * Try to restore session stuff
1049
	 * @return void
1050
	 */
1051
	public function findSessionFilters()
1052
	{
1053
		if ($this->filter || ($this->page != 1) || $this->sort || $this->per_page) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->filter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->sort of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1054
			return;
1055
		}
1056
1057
		if (!$this->remember_state) {
1058
			return;
1059
		}
1060
1061
		if ($page = $this->getSessionData('_grid_page')) {
1062
			$this->page = $page;
1063
		}
1064
1065
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1066
			$this->per_page = $per_page;
1067
		}
1068
1069
		if ($sort = $this->getSessionData('_grid_sort')) {
1070
			$this->sort = $sort;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sort of type * is incompatible with the declared type array of property $sort.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1071
		}
1072
1073 View Code Duplication
		foreach ($this->getSessionData() as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1074
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page'])) {
1075
				$this->filter[$key] = $value;
1076
			}
1077
		}
1078
	}
1079
1080
1081
	/********************************************************************************
1082
	 *                                    EXPORTS                                   *
1083
	 ********************************************************************************/
1084
1085
1086
	/**
1087
	 * Add export of type callback
1088
	 * @param string   $text
1089
	 * @param callable $callback
1090
	 * @param boolean  $filtered
1091
	 */
1092
	public function addExportCallback($text, $callback, $filtered = FALSE)
1093
	{
1094
		if (!is_callable($callback)) {
1095
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1096
		}
1097
1098
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
1099
	}
1100
1101
1102
	/**
1103
	 * Add already implemented csv export
1104
	 * @param string $text
1105
	 * @param string $csv_file_name
1106
	 */
1107
	public function addExportCsv($text, $csv_file_name)
1108
	{
1109
		return $this->addToExports(new Export\ExportCsv($text, $csv_file_name, FALSE));
1110
	}
1111
1112
1113
	/**
1114
	 * Add already implemented csv export, but for filtered data
1115
	 * @param string $text
1116
	 * @param string $csv_file_name
1117
	 */
1118
	public function addExportCsvFiltered($text, $csv_file_name)
1119
	{
1120
		return $this->addToExports(new Export\ExportCsv($text, $csv_file_name, TRUE));
1121
	}
1122
1123
1124
	/**
1125
	 * Add export to array
1126
	 * @param Export\Export $export
1127
	 */
1128
	protected function addToExports(Export\Export $export)
1129
	{
1130
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1131
1132
		$export->setLink($this->link('export!', ['id' => $id]));
1133
1134
		return $this->exports[$id] = $export;
1135
	}
1136
1137
1138
	public function resetExportsLinks()
1139
	{
1140
		foreach ($this->exports as $id => $export) {
1141
			$export->setLink($this->link('export!', ['id' => $id]));
1142
		}
1143
	}
1144
1145
1146
	/********************************************************************************
1147
	 *                                 GROUP ACTIONS                                *
1148
	 ********************************************************************************/
1149
1150
1151
	/**
1152
	 * Add group actino
1153
	 * @param string $title
1154
	 * @param array  $options
1155
	 */
1156
	public function addGroupAction($title, $options = [])
1157
	{
1158
		return $this->getGroupActionCollection()->addGroupAction($title, $options);
1159
	}
1160
1161
1162
	/**
1163
	 * Get collection of all group actions
1164
	 * @return GroupAction\GroupActionCollection
1165
	 */
1166
	public function getGroupActionCollection()
1167
	{
1168
		if (!$this->group_action_collection) {
1169
			$this->group_action_collection = new GroupAction\GroupActionCollection();
1170
		}
1171
1172
		return $this->group_action_collection;
1173
	}
1174
1175
1176
	/**
1177
	 * Has datagrid some group actions?
1178
	 * @return boolean
1179
	 */
1180
	public function hasGroupActions()
1181
	{
1182
		return (bool) $this->group_action_collection;
1183
	}
1184
1185
1186
	/********************************************************************************
1187
	 *                                   HANDLERS                                   *
1188
	 ********************************************************************************/
1189
1190
1191
	/**
1192
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1193
	 * @param  int  $page
1194
	 * @return void
1195
	 */
1196
	public function handlePage($page)
1197
	{
1198
		/**
1199
		 * Session stuff
1200
		 */
1201
		$this->page = $page;
1202
		$this->saveSessionData('_grid_page', $page);
1203
1204
		$this->reload(['table']);
1205
	}
1206
1207
1208
	/**
1209
	 * Handler for sorting
1210
	 * @return void
1211
	 */
1212
	public function handleSort(array $sort)
1213
	{
1214
		/**
1215
		 * Session stuff
1216
		 */
1217
		$this->sort = $sort;
1218
		$this->saveSessionData('_grid_sort', $this->sort);
1219
1220
		$this->reload(['table']);
1221
	}
1222
1223
1224
	/**
1225
	 * handler for reseting the filter
1226
	 * @return void
1227
	 */
1228
	public function handleResetFilter()
1229
	{
1230
		/**
1231
		 * Session stuff
1232
		 */
1233
		$this->deleteSesssionData('_grid_page');
1234
1235 View Code Duplication
		foreach ($this->getSessionData() as $key => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1236
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page'])) {
1237
				$this->deleteSesssionData($key);
1238
			}
1239
		}
1240
1241
		$this->filter = [];
1242
1243
		$this->reload(['grid']);
1244
	}
1245
1246
1247
	/**
1248
	 * Handler for export
1249
	 * @param  int $id Key for particular export class in array $this->exports
1250
	 * @return void
1251
	 */
1252
	public function handleExport($id)
1253
	{
1254
		if (!isset($this->exports[$id])) {
1255
			throw new Nette\Application\ForbiddenRequestException;
1256
		}
1257
1258
		if ($this->columns_export_order) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->columns_export_order of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1259
			$this->setColumnsOrder($this->columns_export_order);
1260
		}
1261
1262
		$export = $this->exports[$id];
1263
1264
		if ($export->isFiltered()) {
1265
			$sort      = $this->sort;
1266
			$filter    = $this->assableFilters();
1267
		} else {
1268
			$sort      = $this->primary_key;
1269
			$filter    = [];
1270
		}
1271
1272
		if (NULL === $this->dataModel) {
1273
			throw new DataGridException('You have to set a data source first.');
1274
		}
1275
1276
		$rows = [];
1277
1278
		$items = Nette\Utils\Callback::invokeArgs(
1279
			[$this->dataModel, 'filterData'], [NULL, $sort, $filter]
1280
		);
1281
1282
		foreach ($items as $item) {
1283
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1284
		}
1285
1286
		if ($export instanceof Export\ExportCsv) {
1287
			$export->invoke($rows, $this);
1288
		} else {
1289
			$export->invoke($items, $this);
1290
		}
1291
1292
		if ($export->isAjax()) {
1293
			$this->reload();
1294
		}
1295
	}
1296
1297
1298
	/**
1299
	 * Handler for getting children of parent item (e.g. category)
1300
	 * @param  int $parent
1301
	 * @return void
1302
	 */
1303 View Code Duplication
	public function handleGetChildren($parent)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1304
	{
1305
		$this->setDataSource(
1306
			call_user_func($this->tree_view_children_callback, $parent)
1307
		);
1308
1309
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1310
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1311
			$this->getPresenter()->payload->_datagrid_tree = $parent;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1312
1313
			$this->redrawControl('items');
1314
1315
			$this->onRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onRedraw does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1316
		} else {
1317
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1318
		}
1319
	}
1320
1321
1322
	/**
1323
	 * Handler for getting item detail
1324
	 * @param  mixed $id
1325
	 * @return void
1326
	 */
1327 View Code Duplication
	public function handleGetItemDetail($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1328
	{
1329
		$this->template->toggle_detail = $id;
0 ignored issues
show
Documentation introduced by
The property $template is declared private in Nette\Application\UI\Control. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
Accessing toggle_detail on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1330
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
0 ignored issues
show
Bug introduced by
The method getPrimaryWhereColumn cannot be called on $this->items_detail (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1331
1332
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1333
			$this->getPresenter()->payload->_datagrid_toggle_detail = $id;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1334
			$this->redrawControl('items');
1335
1336
			$this->onRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onRedraw does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1337
		} else {
1338
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1339
		}
1340
	}
1341
1342
1343
	/**
1344
	 * Handler for inline editing
1345
	 * @param  mixed $id
1346
	 * @param  mixed $key
1347
	 * @return void
1348
	 */
1349
	public function handleEdit($id, $key)
1350
	{
1351
		$column = $this->getColumn($key);
1352
		$value = $this->getPresenter()->getRequest()->getPost('value');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getRequest() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1353
1354
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1355
	}
1356
1357
1358
	/**
1359
	 * Redraw $this
1360
	 * @return void
1361
	 */
1362
	public function reload($snippets = [])
1363
	{
1364
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1365
			$this->redrawControl('tbody');
1366
			$this->redrawControl('pagination');
1367
1368
			/**
1369
			 * manualy reset exports links...
1370
			 */
1371
			$this->resetExportsLinks();
1372
			$this->redrawControl('exports');
1373
1374
			foreach ($snippets as $snippet) {
1375
				$this->redrawControl($snippet);
1376
			}
1377
1378
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1379
1380
			$this->onRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onRedraw does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1381
		} else {
1382
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1383
		}
1384
	}
1385
1386
1387
	/**
1388
	 * Redraw just one row via ajax
1389
	 * @param  int   $id
1390
	 * @param  mixed $primary_where_column
1391
	 * @return void
1392
	 */
1393
	public function redrawItem($id, $primary_where_column = NULL)
1394
	{
1395
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1396
1397
		$this->redrawControl('items');
1398
		$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1399
1400
		$this->onRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onRedraw does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1401
	}
1402
1403
1404
	/********************************************************************************
1405
	 *                                  PAGINATION                                  *
1406
	 ********************************************************************************/
1407
1408
1409
	/**
1410
	 * Set options of select "items_per_page"
1411
	 * @param array $items_per_page_list
1412
	 */
1413
	public function setItemsPerPageList(array $items_per_page_list)
1414
	{
1415
		$this->items_per_page_list = $items_per_page_list;
1416
1417
		return $this;
1418
	}
1419
1420
1421
	/**
1422
	 * Paginator factory
1423
	 * @return Components\DataGridPaginator\DataGridPaginator
1424
	 */
1425
	public function createComponentPaginator()
1426
	{
1427
		/**
1428
		 * Init paginator
1429
		 */
1430
		$component = new Components\DataGridPaginator\DataGridPaginator;
1431
		$paginator = $component->getPaginator();
1432
1433
		$paginator->setPage($this->page);
1434
		$paginator->setItemsPerPage($this->getPerPage());
1435
1436
		return $component;
1437
	}
1438
1439
1440
	/**
1441
	 * PerPage form factory
1442
	 * @return Form
1443
	 */
1444
	public function createComponentPerPage()
1445
	{
1446
		$form = new Form;
1447
1448
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1449
			->setValue($this->getPerPage());
1450
1451
		$form->addSubmit('submit', '');
1452
1453
		$saveSessionData = [$this, 'saveSessionData'];
1454
1455
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
1456
			/**
1457
			 * Session stuff
1458
			 */
1459
			$saveSessionData('_grid_per_page', $values->per_page);
1460
1461
			/**
1462
			 * Other stuff
1463
			 */
1464
			$this->per_page = $values->per_page;
1465
			$this->reload();
1466
		};
1467
1468
		return $form;
1469
	}
1470
1471
1472
	/**
1473
	 * Get parameter per_page
1474
	 * @return int
1475
	 */
1476
	public function getPerPage()
1477
	{
1478
		$per_page = $this->per_page ?: reset($this->items_per_page_list);
1479
1480
		if ($per_page !== 'all' && !in_array($this->per_page, $this->items_per_page_list)) {
1481
			$per_page = reset($this->items_per_page_list);
1482
		}
1483
1484
		return $per_page;
1485
	}
1486
1487
1488
	/**
1489
	 * Get associative array of items_per_page_list
1490
	 * @return array
1491
	 */
1492
	public function getItemsPerPageList()
1493
	{
1494
		$list = array_flip($this->items_per_page_list);
1495
1496
		foreach ($list as $key => $value) {
1497
			$list[$key] = $key;
1498
		}
1499
1500
		$list['all'] = $this->getTranslator()->translate('All');
1501
1502
		return $list;
1503
	}
1504
1505
1506
	/**
1507
	 * Order Grid to "be paginated"
1508
	 * @param bool $do
1509
	 */
1510
	public function setPagination($do)
1511
	{
1512
		$this->do_paginate = (bool) $do;
1513
1514
		return $this;
1515
	}
1516
1517
1518
	/**
1519
	 * Tell whether Grid is paginated
1520
	 * @return bool
1521
	 */
1522
	public function isPaginated()
1523
	{
1524
		return $this->do_paginate;
1525
	}
1526
1527
1528
	/**
1529
	 * Return current paginator class
1530
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
1531
	 */
1532
	public function getPaginator()
1533
	{
1534
		if ($this->isPaginated() && $this->per_page !== 'all') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $this->per_page (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
1535
			return $this['paginator'];
1536
		}
1537
1538
		return NULL;
1539
	}
1540
1541
1542
	/********************************************************************************
1543
	 *                                     I18N                                     *
1544
	 ********************************************************************************/
1545
1546
1547
	/**
1548
	 * Set datagrid translator
1549
	 * @param Nette\Localization\ITranslator $translator
1550
	 */
1551
	public function setTranslator(Nette\Localization\ITranslator $translator)
1552
	{
1553
		$this->translator = $translator;
1554
1555
		return $this;
1556
	}
1557
1558
1559
	/**
1560
	 * Get translator for datagrid
1561
	 * @return Nette\Localization\ITranslator
1562
	 */
1563
	public function getTranslator()
1564
	{
1565
		if (!$this->translator) {
1566
			$this->translator = new Localization\SimpleTranslator;
1567
		}
1568
1569
		return $this->translator;
1570
	}
1571
1572
1573
	/********************************************************************************
1574
	 *                                 COLUMNS ORDER                                *
1575
	 ********************************************************************************/
1576
1577
1578
	/**
1579
	 * Set order of datagrid columns
1580
	 * @param array $order
1581
	 */
1582
	public function setColumnsOrder($order)
1583
	{
1584
		$new_order = [];
1585
1586
		foreach ($order as $key) {
1587
			if (isset($this->columns[$key])) {
1588
				$new_order[$key] = $this->columns[$key];
1589
			}
1590
		}
1591
1592
		if (sizeof($new_order) === sizeof($this->columns)) {
1593
			$this->columns = $new_order;
1594
		} else {
1595
			throw new DataGridException('When changing columns order, you have to specify all columns');
1596
		}
1597
1598
		return $this;
1599
	}
1600
1601
1602
	/**
1603
	 * Columns order may be different for export and normal grid
1604
	 * @param array $order
1605
	 */
1606
	public function setColumnsExportOrder($order)
1607
	{
1608
		$this->columns_export_order = (array) $order;
1609
	}
1610
1611
1612
	/********************************************************************************
1613
	 *                                SESSION & URL                                 *
1614
	 ********************************************************************************/
1615
1616
1617
	/**
1618
	 * Find some unique session key name
1619
	 * @return string
1620
	 */
1621
	public function getSessionSectionName()
1622
	{
1623
		return $this->getPresenter()->getName().':'.$this->getName();
1624
	}
1625
1626
1627
	/**
1628
	 * Should datagrid remember its filters/pagination/etc using session?
1629
	 * @param bool $remember
1630
	 */
1631
	public function setRememberState($remember = TRUE)
1632
	{
1633
		$this->remember_state = (bool) $remember;
1634
1635
		return $this;
1636
	}
1637
1638
1639
	/**
1640
	 * Should datagrid refresh url using history API?
1641
	 * @param bool $refresh
1642
	 */
1643
	public function setRefreshUrl($refresh = TRUE)
1644
	{
1645
		$this->refresh_url = (bool) $refresh;
1646
1647
1648
		return $this;
1649
	}
1650
1651
1652
	/**
1653
	 * Get session data if functionality is enabled
1654
	 * @param  string $key
1655
	 * @return mixed
1656
	 */
1657
	public function getSessionData($key = NULL)
1658
	{
1659
		if (!$this->remember_state) {
1660
			return $key ? NULL : [];
1661
		}
1662
1663
		return $key ? $this->grid_session->{$key} : $this->grid_session;
1664
	}
1665
1666
1667
	/**
1668
	 * Save session data - just if it is enabled
1669
	 * @param  string $key
1670
	 * @param  mixed  $value
1671
	 * @return void
1672
	 */
1673
	public function saveSessionData($key, $value)
1674
	{
1675
1676
		if ($this->remember_state) {
1677
			$this->grid_session->{$key} = $value;
1678
		}
1679
	}
1680
1681
1682
	/**
1683
	 * Delete session data
1684
	 * @return void
1685
	 */
1686
	public function deleteSesssionData($key)
1687
	{
1688
		unset($this->grid_session->{$key});
1689
	}
1690
1691
1692
	/********************************************************************************
1693
	 *                                  ITEM DETAIL                                 *
1694
	 ********************************************************************************/
1695
1696
1697
	/**
1698
	 * Get items detail parameters
1699
	 * @return array
1700
	 */
1701
	public function getItemsDetail()
1702
	{
1703
		return $this->items_detail;
1704
	}
1705
1706
1707
	/**
1708
	 * Items can have thair detail - toggled
1709
	 * @param mixed $detail callable|string|bool
1710
	 */
1711
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
1712
	{
1713
		if ($this->isSortable()) {
1714
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
1715
		}
1716
1717
		$this->items_detail = new Column\ItemDetail(
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Ublaboo\DataGrid\Co... ?: $this->primary_key) of type object<Ublaboo\DataGrid\Column\ItemDetail> is incompatible with the declared type array of property $items_detail.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1718
			$this,
1719
			$primary_where_column ?: $this->primary_key
1720
		);
1721
1722
		if (is_string($detail)) {
1723
			/**
1724
			 * Item detail will be in separate template
1725
			 */
1726
			$this->items_detail->setType('template');
1727
			$this->items_detail->setTemplate($detail);
1728
1729
		} else if (is_callable($detail)) {
1730
			/**
1731
			 * Item detail will be rendered via custom callback renderer
1732
			 */
1733
			$this->items_detail->setType('renderer');
1734
			$this->items_detail->setRenderer($detail);
1735
1736
		} else if (TRUE === $detail) {
1737
			/**
1738
			 * Item detail will be rendered probably via block #detail
1739
			 */
1740
			$this->items_detail->setType('block');
1741
1742
		} else {
1743
			throw new DataGridException(
1744
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
1745
			);
1746
		}
1747
1748
		return $this->items_detail;
1749
	}
1750
1751
1752
	/********************************************************************************
1753
	 *                                ROW PRIVILEGES                                *
1754
	 ********************************************************************************/
1755
1756
1757
	public function allowRowsGroupAction(callable $condition)
1758
	{
1759
		$this->row_conditions['group_action'] = $condition;
1760
	}
1761
1762
1763
	public function allowRowsAction($key, callable $condition)
1764
	{
1765
		$this->row_conditions['action'][$key] = $condition;
1766
	}
1767
1768
1769
	public function getRowCondition($name, $key = NULL)
1770
	{
1771
		if (!isset($this->row_conditions[$name])) {
1772
			return FALSE;
1773
		}
1774
1775
		$condition = $this->row_conditions[$name];
1776
1777
		if (!$key) {
1778
			return $condition;
1779
		}
1780
1781
		return isset($condition[$key]) ? $condition[$key] : FALSE;
1782
	}
1783
1784
1785
	/********************************************************************************
1786
	 *                               HIDEABLE COLUMNS                               *
1787
	 ********************************************************************************/
1788
1789
1790
	/**
1791
	 * Can datagrid hide colums?
1792
	 * @return boolean
1793
	 */
1794
	public function canHideColumns()
1795
	{
1796
		return (bool) $this->can_hide_columns;
1797
	}
1798
1799
1800
	/**
1801
	 * Order Grid to set columns hideable.
1802
	 * @param bool $do
0 ignored issues
show
Bug introduced by
There is no parameter named $do. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1803
	 */
1804
	public function setColumnsHideable()
1805
	{
1806
		$this->can_hide_columns = TRUE;
1807
1808
		return $this;
1809
	}
1810
1811
1812
	/********************************************************************************
1813
	 *                                   INTERNAL                                   *
1814
	 ********************************************************************************/
1815
1816
1817
	/**
1818
	 * Get cont of columns
1819
	 * @return int
1820
	 */
1821
	public function getColumnsCount()
1822
	{
1823
		$count = sizeof($this->columns);
1824
1825
		if ($this->actions || $this->isSortable() || $this->getItemsDetail()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->actions of type Ublaboo\DataGrid\Column\Action[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1826
			$count++;
1827
		}
1828
1829
		if ($this->hasGroupActions()) {
1830
			$count++;
1831
		}
1832
1833
		return $count;
1834
	}
1835
1836
1837
	/**
1838
	 * Get primary key of datagrid data source
1839
	 * @return string
1840
	 */
1841
	public function getPrimaryKey()
1842
	{
1843
		return $this->primary_key;
1844
	}
1845
1846
1847
	/**
1848
	 * Get set of set columns
1849
	 * @return Column\IColumn[]
1850
	 */
1851
	public function getColumns()
1852
	{
1853
		return $this->columns;
1854
	}
1855
1856
}
1857
1858
1859
class DataGridException extends \Exception
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
1860
{
1861
}
1862