Completed
Push — master ( 0aea12...e28893 )
by Pavel
02:47
created

DataGrid::handleGetItemDetail()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 14
Ratio 100 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 14
loc 14
rs 9.4285
cc 2
eloc 9
nc 2
nop 1
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->getColumns();
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
		foreach ($this->getSessionData() as $key => $value) {
1074
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page', '_grid_hidden_columns'])) {
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
		foreach ($this->getSessionData() as $key => $value) {
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
	 * Tell datagrid to display all columns
1406
	 * @return void
1407
	 */
1408
	public function handleShowAllColumns()
1409
	{
1410
		$this->deleteSesssionData('_grid_hidden_columns');
1411
1412
		$this->redrawControl();
1413
1414
		$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...
1415
	}
1416
1417
1418
	/**
1419
	 * Notice datagrid to not display particular columns
1420
	 * @param  string $column
1421
	 * @return void
1422
	 */
1423
	public function handleHideColumn($column)
1424
	{
1425
		/**
1426
		 * Store info about hiding a column to session
1427
		 */
1428
		$columns = $this->getSessionData('_grid_hidden_columns');
1429
1430
		if (empty($columns)) {
1431
			$columns = [$column];
1432
		} else if (!in_array($column, $columns)) {
1433
			array_push($columns, $column);
1434
		}
1435
1436
		$this->saveSessionData('_grid_hidden_columns', $columns);
1437
1438
		$this->redrawControl();
1439
1440
		$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...
1441
	}
1442
1443
1444
	/********************************************************************************
1445
	 *                                  PAGINATION                                  *
1446
	 ********************************************************************************/
1447
1448
1449
	/**
1450
	 * Set options of select "items_per_page"
1451
	 * @param array $items_per_page_list
1452
	 */
1453
	public function setItemsPerPageList(array $items_per_page_list)
1454
	{
1455
		$this->items_per_page_list = $items_per_page_list;
1456
1457
		return $this;
1458
	}
1459
1460
1461
	/**
1462
	 * Paginator factory
1463
	 * @return Components\DataGridPaginator\DataGridPaginator
1464
	 */
1465
	public function createComponentPaginator()
1466
	{
1467
		/**
1468
		 * Init paginator
1469
		 */
1470
		$component = new Components\DataGridPaginator\DataGridPaginator;
1471
		$paginator = $component->getPaginator();
1472
1473
		$paginator->setPage($this->page);
1474
		$paginator->setItemsPerPage($this->getPerPage());
1475
1476
		return $component;
1477
	}
1478
1479
1480
	/**
1481
	 * PerPage form factory
1482
	 * @return Form
1483
	 */
1484
	public function createComponentPerPage()
1485
	{
1486
		$form = new Form;
1487
1488
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1489
			->setValue($this->getPerPage());
1490
1491
		$form->addSubmit('submit', '');
1492
1493
		$saveSessionData = [$this, 'saveSessionData'];
1494
1495
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
1496
			/**
1497
			 * Session stuff
1498
			 */
1499
			$saveSessionData('_grid_per_page', $values->per_page);
1500
1501
			/**
1502
			 * Other stuff
1503
			 */
1504
			$this->per_page = $values->per_page;
1505
			$this->reload();
1506
		};
1507
1508
		return $form;
1509
	}
1510
1511
1512
	/**
1513
	 * Get parameter per_page
1514
	 * @return int
1515
	 */
1516
	public function getPerPage()
1517
	{
1518
		$per_page = $this->per_page ?: reset($this->items_per_page_list);
1519
1520
		if ($per_page !== 'all' && !in_array($this->per_page, $this->items_per_page_list)) {
1521
			$per_page = reset($this->items_per_page_list);
1522
		}
1523
1524
		return $per_page;
1525
	}
1526
1527
1528
	/**
1529
	 * Get associative array of items_per_page_list
1530
	 * @return array
1531
	 */
1532
	public function getItemsPerPageList()
1533
	{
1534
		$list = array_flip($this->items_per_page_list);
1535
1536
		foreach ($list as $key => $value) {
1537
			$list[$key] = $key;
1538
		}
1539
1540
		$list['all'] = $this->getTranslator()->translate('All');
1541
1542
		return $list;
1543
	}
1544
1545
1546
	/**
1547
	 * Order Grid to "be paginated"
1548
	 * @param bool $do
1549
	 */
1550
	public function setPagination($do)
1551
	{
1552
		$this->do_paginate = (bool) $do;
1553
1554
		return $this;
1555
	}
1556
1557
1558
	/**
1559
	 * Tell whether Grid is paginated
1560
	 * @return bool
1561
	 */
1562
	public function isPaginated()
1563
	{
1564
		return $this->do_paginate;
1565
	}
1566
1567
1568
	/**
1569
	 * Return current paginator class
1570
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
1571
	 */
1572
	public function getPaginator()
1573
	{
1574
		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...
1575
			return $this['paginator'];
1576
		}
1577
1578
		return NULL;
1579
	}
1580
1581
1582
	/********************************************************************************
1583
	 *                                     I18N                                     *
1584
	 ********************************************************************************/
1585
1586
1587
	/**
1588
	 * Set datagrid translator
1589
	 * @param Nette\Localization\ITranslator $translator
1590
	 */
1591
	public function setTranslator(Nette\Localization\ITranslator $translator)
1592
	{
1593
		$this->translator = $translator;
1594
1595
		return $this;
1596
	}
1597
1598
1599
	/**
1600
	 * Get translator for datagrid
1601
	 * @return Nette\Localization\ITranslator
1602
	 */
1603
	public function getTranslator()
1604
	{
1605
		if (!$this->translator) {
1606
			$this->translator = new Localization\SimpleTranslator;
1607
		}
1608
1609
		return $this->translator;
1610
	}
1611
1612
1613
	/********************************************************************************
1614
	 *                                 COLUMNS ORDER                                *
1615
	 ********************************************************************************/
1616
1617
1618
	/**
1619
	 * Set order of datagrid columns
1620
	 * @param array $order
1621
	 */
1622
	public function setColumnsOrder($order)
1623
	{
1624
		$new_order = [];
1625
1626
		foreach ($order as $key) {
1627
			if (isset($this->columns[$key])) {
1628
				$new_order[$key] = $this->columns[$key];
1629
			}
1630
		}
1631
1632
		if (sizeof($new_order) === sizeof($this->columns)) {
1633
			$this->columns = $new_order;
1634
		} else {
1635
			throw new DataGridException('When changing columns order, you have to specify all columns');
1636
		}
1637
1638
		return $this;
1639
	}
1640
1641
1642
	/**
1643
	 * Columns order may be different for export and normal grid
1644
	 * @param array $order
1645
	 */
1646
	public function setColumnsExportOrder($order)
1647
	{
1648
		$this->columns_export_order = (array) $order;
1649
	}
1650
1651
1652
	/********************************************************************************
1653
	 *                                SESSION & URL                                 *
1654
	 ********************************************************************************/
1655
1656
1657
	/**
1658
	 * Find some unique session key name
1659
	 * @return string
1660
	 */
1661
	public function getSessionSectionName()
1662
	{
1663
		return $this->getPresenter()->getName().':'.$this->getName();
1664
	}
1665
1666
1667
	/**
1668
	 * Should datagrid remember its filters/pagination/etc using session?
1669
	 * @param bool $remember
1670
	 */
1671
	public function setRememberState($remember = TRUE)
1672
	{
1673
		$this->remember_state = (bool) $remember;
1674
1675
		return $this;
1676
	}
1677
1678
1679
	/**
1680
	 * Should datagrid refresh url using history API?
1681
	 * @param bool $refresh
1682
	 */
1683
	public function setRefreshUrl($refresh = TRUE)
1684
	{
1685
		$this->refresh_url = (bool) $refresh;
1686
1687
1688
		return $this;
1689
	}
1690
1691
1692
	/**
1693
	 * Get session data if functionality is enabled
1694
	 * @param  string $key
1695
	 * @return mixed
1696
	 */
1697
	public function getSessionData($key = NULL, $default_value = NULL)
1698
	{
1699
		if (!$this->remember_state) {
1700
			return $key ? $default_value : [];
1701
		}
1702
1703
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
1704
	}
1705
1706
1707
	/**
1708
	 * Save session data - just if it is enabled
1709
	 * @param  string $key
1710
	 * @param  mixed  $value
1711
	 * @return void
1712
	 */
1713
	public function saveSessionData($key, $value)
1714
	{
1715
		if ($this->remember_state) {
1716
			$this->grid_session->{$key} = $value;
1717
		}
1718
	}
1719
1720
1721
	/**
1722
	 * Delete session data
1723
	 * @return void
1724
	 */
1725
	public function deleteSesssionData($key)
1726
	{
1727
		unset($this->grid_session->{$key});
1728
	}
1729
1730
1731
	/********************************************************************************
1732
	 *                                  ITEM DETAIL                                 *
1733
	 ********************************************************************************/
1734
1735
1736
	/**
1737
	 * Get items detail parameters
1738
	 * @return array
1739
	 */
1740
	public function getItemsDetail()
1741
	{
1742
		return $this->items_detail;
1743
	}
1744
1745
1746
	/**
1747
	 * Items can have thair detail - toggled
1748
	 * @param mixed $detail callable|string|bool
1749
	 */
1750
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
1751
	{
1752
		if ($this->isSortable()) {
1753
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
1754
		}
1755
1756
		$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...
1757
			$this,
1758
			$primary_where_column ?: $this->primary_key
1759
		);
1760
1761
		if (is_string($detail)) {
1762
			/**
1763
			 * Item detail will be in separate template
1764
			 */
1765
			$this->items_detail->setType('template');
1766
			$this->items_detail->setTemplate($detail);
1767
1768
		} else if (is_callable($detail)) {
1769
			/**
1770
			 * Item detail will be rendered via custom callback renderer
1771
			 */
1772
			$this->items_detail->setType('renderer');
1773
			$this->items_detail->setRenderer($detail);
1774
1775
		} else if (TRUE === $detail) {
1776
			/**
1777
			 * Item detail will be rendered probably via block #detail
1778
			 */
1779
			$this->items_detail->setType('block');
1780
1781
		} else {
1782
			throw new DataGridException(
1783
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
1784
			);
1785
		}
1786
1787
		return $this->items_detail;
1788
	}
1789
1790
1791
	/********************************************************************************
1792
	 *                                ROW PRIVILEGES                                *
1793
	 ********************************************************************************/
1794
1795
1796
	public function allowRowsGroupAction(callable $condition)
1797
	{
1798
		$this->row_conditions['group_action'] = $condition;
1799
	}
1800
1801
1802
	public function allowRowsAction($key, callable $condition)
1803
	{
1804
		$this->row_conditions['action'][$key] = $condition;
1805
	}
1806
1807
1808
	public function getRowCondition($name, $key = NULL)
1809
	{
1810
		if (!isset($this->row_conditions[$name])) {
1811
			return FALSE;
1812
		}
1813
1814
		$condition = $this->row_conditions[$name];
1815
1816
		if (!$key) {
1817
			return $condition;
1818
		}
1819
1820
		return isset($condition[$key]) ? $condition[$key] : FALSE;
1821
	}
1822
1823
1824
	/********************************************************************************
1825
	 *                               HIDEABLE COLUMNS                               *
1826
	 ********************************************************************************/
1827
1828
1829
	/**
1830
	 * Can datagrid hide colums?
1831
	 * @return boolean
1832
	 */
1833
	public function canHideColumns()
1834
	{
1835
		return (bool) $this->can_hide_columns;
1836
	}
1837
1838
1839
	/**
1840
	 * Order Grid to set columns hideable.
1841
	 * @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...
1842
	 */
1843
	public function setColumnsHideable()
1844
	{
1845
		$this->can_hide_columns = TRUE;
1846
1847
		return $this;
1848
	}
1849
1850
1851
	/********************************************************************************
1852
	 *                                   INTERNAL                                   *
1853
	 ********************************************************************************/
1854
1855
1856
	/**
1857
	 * Get cont of columns
1858
	 * @return int
1859
	 */
1860
	public function getColumnsCount()
1861
	{
1862
		$count = sizeof($this->getColumns());
1863
1864
		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...
1865
			$count++;
1866
		}
1867
1868
		if ($this->hasGroupActions()) {
1869
			$count++;
1870
		}
1871
1872
		return $count;
1873
	}
1874
1875
1876
	/**
1877
	 * Get primary key of datagrid data source
1878
	 * @return string
1879
	 */
1880
	public function getPrimaryKey()
1881
	{
1882
		return $this->primary_key;
1883
	}
1884
1885
1886
	/**
1887
	 * Get set of set columns
1888
	 * @return Column\IColumn[]
1889
	 */
1890
	public function getColumns()
1891
	{
1892
		foreach ($this->getSessionData('_grid_hidden_columns', []) as $column) {
1893
			$this->removeColumn($column);
1894
		}
1895
1896
		return $this->columns;
1897
	}
1898
1899
}
1900
1901
1902
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...
1903
{
1904
}
1905