Completed
Push — master ( 80c93d...5b1d90 )
by Pavel
06:32
created

DataGrid::handleChangeStatus()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 3
eloc 6
nc 3
nop 3
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 Nette\Application\UI\PresenterComponent;
13
use Ublaboo\DataGrid\Utils\ArraysHelper;
14
use Nette\Application\UI\Form;
15
use Ublaboo\DataGrid\Exception\DataGridException;
16
use Ublaboo\DataGrid\Exception\DataGridHasToBeAttachedToPresenterComponentException;
17
18
class DataGrid extends Nette\Application\UI\Control
19
{
20
21
	/**
22
	 * @var callable[]
23
	 */
24
	public $onRedraw;
25
26
	/**
27
	 * @var string
28
	 */
29
	public static $icon_prefix = 'fa fa-';
30
31
	/**
32
	 * When set to TRUE, datagrid throws an exception
33
	 * 	when tring to get related entity within join and entity does not exist
34
	 * @var bool
35
	 */
36
	public $strict_entity_property = FALSE;
37
38
	/**
39
	 * @var int
40
	 * @persistent
41
	 */
42
	public $page = 1;
43
44
	/**
45
	 * @var int
46
	 * @persistent
47
	 */
48
	public $per_page;
49
50
	/**
51
	 * @var array
52
	 * @persistent
53
	 */
54
	public $sort = [];
55
56
	/**
57
	 * @var array
58
	 */
59
	public $default_sort = [];
60
61
	/**
62
	 * @var array
63
	 * @persistent
64
	 */
65
	public $filter = [];
66
67
	/**
68
	 * @var Callable[]
69
	 */
70
	public $onRender = [];
71
72
	/**
73
	 * @var bool
74
	 */
75
	protected $use_happy_components = TRUE;
76
77
	/**
78
	 * @var Callable[]
79
	 */
80
	protected $rowCallback;
81
82
	/**
83
	 * @var array
84
	 */
85
	protected $items_per_page_list;
86
87
	/**
88
	 * @var string
89
	 */
90
	protected $template_file;
91
92
	/**
93
	 * @var Column\IColumn[]
94
	 */
95
	protected $columns = [];
96
97
	/**
98
	 * @var Column\Action[]
99
	 */
100
	protected $actions = [];
101
102
	/**
103
	 * @var GroupAction\GroupActionCollection
104
	 */
105
	protected $group_action_collection;
106
107
	/**
108
	 * @var Filter\Filter[]
109
	 */
110
	protected $filters = [];
111
112
	/**
113
	 * @var Export\Export[]
114
	 */
115
	protected $exports = [];
116
117
	/**
118
	 * @var DataModel
119
	 */
120
	protected $dataModel;
121
122
	/**
123
	 * @var DataFilter
124
	 */
125
	protected $dataFilter;
126
127
	/**
128
	 * @var string
129
	 */
130
	protected $primary_key = 'id';
131
132
	/**
133
	 * @var bool
134
	 */
135
	protected $do_paginate = TRUE;
136
137
	/**
138
	 * @var bool
139
	 */
140
	protected $csv_export = TRUE;
141
142
	/**
143
	 * @var bool
144
	 */
145
	protected $csv_export_filtered = TRUE;
146
147
	/**
148
	 * @var bool
149
	 */
150
	protected $sortable = FALSE;
151
152
	/**
153
	 * @var string
154
	 */
155
	protected $sortable_handler = 'sort!';
156
157
	/**
158
	 * @var string
159
	 */
160
	protected $original_template;
161
162
	/**
163
	 * @var array
164
	 */
165
	protected $redraw_item;
166
167
	/**
168
	 * @var mixed
169
	 */
170
	protected $translator;
171
172
	/**
173
	 * @var bool
174
	 */
175
	protected $force_filter_active;
176
177
	/**
178
	 * @var callable
179
	 */
180
	protected $tree_view_children_callback;
181
182
	/**
183
	 * @var string
184
	 */
185
	protected $tree_view_has_children_column;
186
187
	/**
188
	 * @var bool
189
	 */
190
	protected $outer_filter_rendering = FALSE;
191
192
	/**
193
	 * @var array
194
	 */
195
	protected $columns_export_order = [];
196
197
	/**
198
	 * @var bool
199
	 */
200
	private $remember_state = TRUE;
201
202
	/**
203
	 * @var bool
204
	 */
205
	private $refresh_url = TRUE;
206
207
	/**
208
	 * @var Nette\Http\SessionSection
209
	 */
210
	private $grid_session;
211
212
	/**
213
	 * @var Column\ItemDetail
214
	 */
215
	private $items_detail;
216
217
	/**
218
	 * @var array
219
	 */
220
	private $row_conditions = [
221
		'group_action' => FALSE,
222
		'action' => []
223
	];
224
225
	/**
226
	 * @var bool
227
	 */
228
	protected $can_hide_columns = FALSE;
229
230
	/** @var callable[] */
231
	public $onColumnAdd;
232
233
234
	/**
235
	 * @param Nette\ComponentModel\IContainer|NULL $parent
236
	 * @param string                               $name
237
	 */
238
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
239
	{
240
		parent::__construct($parent, $name);
241
242
		$this->monitor('Nette\Application\UI\Presenter');
243
	}
244
245
246
	/**
247
	 * {inheritDoc}
248
	 * @return void
249
	 */
250
	public function attached($presenter)
251
	{
252
		parent::attached($presenter);
253
254
		if ($presenter instanceof Nette\Application\UI\Presenter) {
255
			/**
256
			 * Get session
257
			 */
258
			$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...
259
260
			/**
261
			 * Try to find previous filters/pagination/sort in session
262
			 */
263
			$this->findSessionFilters();
264
			$this->findDefaultSort();
265
		}
266
	}
267
268
269
	/********************************************************************************
270
	 *                                  RENDERING                                   *
271
	 ********************************************************************************/
272
273
274
	/**
275
	 * Render template
276
	 * @return void
277
	 */
278
	public function render()
279
	{
280
		/**
281
		 * Check whether datagrid has set some columns, initiated data source, etc
282
		 */
283
		if (!($this->dataModel instanceof DataModel)) {
284
			throw new DataGridException('You have to set a data source first.');
285
		}
286
287
		if (empty($this->columns)) {
288
			throw new DataGridException('You have to add at least one column.');
289
		}
290
291
		$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...
292
293
		/**
294
		 * Invoke some possible events
295
		 */
296
		$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...
297
298
		/**
299
		 * Prepare data for rendering (datagrid may render just one item)
300
		 */
301
		$rows = [];
302
303
		if (!empty($this->redraw_item)) {
304
			$items = $this->dataModel->filterRow($this->redraw_item);
305
		} else {
306
			$items = Nette\Utils\Callback::invokeArgs(
307
				[$this->dataModel, 'filterData'],
308
				[
309
					$this->getPaginator(),
310
					$this->sort,
311
					$this->assableFilters()
312
				]
313
			);
314
		}
315
316
		$callback = $this->rowCallback ?: NULL;
317
318
		foreach ($items as $item) {
319
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
320
321
			if ($callback) {
322
				$callback($item, $row->getControl());
323
			}
324
		}
325
326
		if ($this->isTreeView()) {
327
			$this->template->add('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...
328
		}
329
330
		$this->template->add('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...
331
332
		$this->template->add('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...
333
		$this->template->add('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...
334
		$this->template->add('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...
335
		$this->template->add('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...
336
337
		$this->template->add('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...
338
		$this->template->add('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...
339
		$this->template->add('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...
340
		$this->template->add('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...
341
342
		/**
343
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
344
		 */
345
		$this->template->add('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...
346
347
		/**
348
		 * Set template file and render it
349
		 */
350
		$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...
351
	}
352
353
354
	/********************************************************************************
355
	 *                                 ROW CALLBACK                                 *
356
	 ********************************************************************************/
357
358
359
	/**
360
	 * Each row can be modified with user callback
361
	 * @param  callable  $callback
362
	 * @return static
363
	 */
364
	public function setRowCallback(callable $callback)
365
	{
366
		$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...
367
368
		return $this;
369
	}
370
371
372
	/********************************************************************************
373
	 *                                 DATA SOURCE                                  *
374
	 ********************************************************************************/
375
376
377
	/**
378
	 * By default ID, you can change that
379
	 * @param string $primary_key
380
	 * @return static
381
	 */
382
	public function setPrimaryKey($primary_key)
383
	{
384
		if ($this->dataModel instanceof DataModel) {
385
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
386
		}
387
388
		$this->primary_key = $primary_key;
389
390
		return $this;
391
	}
392
393
394
	/**
395
	 * Set Grid data source
396
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
397
	 * @return static
398
	 */
399
	public function setDataSource($source)
400
	{
401
		$this->dataModel = new DataModel($source, $this->primary_key);
402
403
		return $this;
404
	}
405
406
407
	/********************************************************************************
408
	 *                                  TEMPLATING                                  *
409
	 ********************************************************************************/
410
411
412
	/**
413
	 * Set custom template file to render
414
	 * @param string $template_file
415
	 * @return static
416
	 */
417
	public function setTemplateFile($template_file)
418
	{
419
		$this->template_file = $template_file;
420
421
		return $this;
422
	}
423
424
425
	/**
426
	 * Get DataGrid template file
427
	 * @return string
428
	 * @return static
429
	 */
430
	public function getTemplateFile()
431
	{
432
		return $this->template_file ?: $this->getOriginalTemplateFile();
433
	}
434
435
436
	/**
437
	 * Get DataGrid original template file
438
	 * @return string
439
	 */
440
	public function getOriginalTemplateFile()
441
	{
442
		return __DIR__.'/templates/datagrid.latte';
443
	}
444
445
446
	/**
447
	 * Tell datagrid wheteher to use or not happy components
448
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
449
	 * @return void|bool
450
	 */
451
	public function useHappyComponents($use = NULL)
452
	{
453
		if (NULL === $use) {
454
			return $this->use_happy_components;
455
		}
456
457
		$this->use_happy_components = (bool) $use;
458
	}
459
460
461
	/********************************************************************************
462
	 *                                   SORTING                                    *
463
	 ********************************************************************************/
464
465
466
	/**
467
	 * Set default sorting
468
	 * @param array $sort
469
	 * @return static
470
	 */
471
	public function setDefaultSort($sort)
472
	{
473
		if (is_string($sort)) {
474
			$sort = [$sort => 'ASC'];
475
		} else {
476
			$sort = (array) $sort;
477
		}
478
479
		$this->default_sort = $sort;
480
481
		return $this;
482
	}
483
484
485
	/**
486
	 * User may set default sorting, apply it
487
	 * @return void
488
	 */
489
	public function findDefaultSort()
490
	{
491
		if (!empty($this->sort)) {
492
			return;
493
		}
494
495
		if ($this->default_sort) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->default_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...
496
			$this->sort = $this->default_sort;
497
		}
498
499
		$this->saveSessionData('_grid_sort', $this->sort);
500
	}
501
502
503
	/**
504
	 * Set grido to be sortable
505
	 * @param bool $sortable
506
	 * @return static
507
	 */
508
	public function setSortable($sortable = TRUE)
509
	{
510
		if ($this->getItemsDetail()) {
511
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
512
		}
513
514
		$this->sortable = (bool) $sortable;
515
516
		return $this;
517
	}
518
519
520
	/**
521
	 * Set sortable handle
522
	 * @param string $handler
523
	 * @return static
524
	 */
525
	public function setSortableHandler($handler = 'sort!')
526
	{
527
		$this->sortable_handler = (string) $handler;
528
529
		return $this;
530
	}
531
532
533
	/**
534
	 * Tell whether DataGrid is sortable
535
	 * @return bool
536
	 */
537
	public function isSortable()
538
	{
539
		return $this->sortable;
540
	}
541
542
	/**
543
	 * Return sortable handle name
544
	 * @return string
545
	 */
546
	public function getSortableHandler()
547
	{
548
		return $this->sortable_handler;
549
	}
550
551
552
	/********************************************************************************
553
	 *                                  TREE VIEW                                   *
554
	 ********************************************************************************/
555
556
557
	/**
558
	 * Is tree view set?
559
	 * @return boolean
560
	 */
561
	public function isTreeView()
562
	{
563
		return (bool) $this->tree_view_children_callback;
564
	}
565
566
567
	/**
568
	 * Setting tree view
569
	 * @param callable $get_children_callback
570
	 * @param string $tree_view_has_children_column
571
	 * @return static
572
	 */
573
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
574
	{
575
		if (!is_callable($get_children_callback)) {
576
			throw new DataGridException(
577
				'Parameters to method DataGrid::setTreeView must be of type callable'
578
			);
579
		}
580
581
		$this->tree_view_children_callback = $get_children_callback;
582
		$this->tree_view_has_children_column = $tree_view_has_children_column;
583
584
		/**
585
		 * TUrn off pagination
586
		 */
587
		$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...
588
589
		/**
590
		 * Set tree view template file
591
		 */
592
		if (!$this->template_file) {
593
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
594
		}
595
596
		return $this;
597
	}
598
599
600
	/********************************************************************************
601
	 *                                    COLUMNS                                   *
602
	 ********************************************************************************/
603
604
605
	/**
606
	 * Add text column with no other formating
607
	 * @param  string      $key
608
	 * @param  string      $name
609
	 * @param  string|null $column
610
	 * @return Column\ColumnText
611
	 */
612
	public function addColumnText($key, $name, $column = NULL)
613
	{
614
		$this->addColumnCheck($key);
615
		$column = $column ?: $key;
616
617
		return $this->addColumn($key, new Column\ColumnText($this, $column, $name));
618
	}
619
620
621
	/**
622
	 * Add column with link
623
	 * @param  string      $key
624
	 * @param  string      $name
625
	 * @param  string|null $column
626
	 * @return Column\ColumnLink
627
	 */
628
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
629
	{
630
		$this->addColumnCheck($key);
631
		$column = $column ?: $key;
632
		$href = $href ?: $key;
633
634
		if (NULL === $params) {
635
			$params = [$this->primary_key];
636
		}
637
638
		return $this->addColumn($key, new Column\ColumnLink($this, $column, $name, $href, $params));
639
	}
640
641
642
	/**
643
	 * Add column with possible number formating
644
	 * @param  string      $key
645
	 * @param  string      $name
646
	 * @param  string|null $column
647
	 * @return Column\ColumnNumber
648
	 */
649
	public function addColumnNumber($key, $name, $column = NULL)
650
	{
651
		$this->addColumnCheck($key);
652
		$column = $column ?: $key;
653
654
		return $this->addColumn($key, new Column\ColumnNumber($this, $column, $name));
655
	}
656
657
658
	/**
659
	 * Add column with date formating
660
	 * @param  string      $key
661
	 * @param  string      $name
662
	 * @param  string|null $column
663
	 * @return Column\ColumnDateTime
664
	 */
665
	public function addColumnDateTime($key, $name, $column = NULL)
666
	{
667
		$this->addColumnCheck($key);
668
		$column = $column ?: $key;
669
670
		return $this->addColumn($key, new Column\ColumnDateTime($this, $column, $name));
671
	}
672
673
674
	/**
675
	 * Add column status
676
	 * @param  string      $key
677
	 * @param  string      $name
678
	 * @param  string|null $column
679
	 * @return Column\ColumnStatus
680
	 */
681
	public function addColumnStatus($key, $name, $column = NULL)
682
	{
683
		$this->addColumnCheck($key);
684
		$column = $column ?: $key;
685
686
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
687
	}
688
689
690
	/**
691
	 * @param string $key
692
	 * @param Column\Column $column
693
	 * @return Column\Column
694
	 */
695
	protected function addColumn($key, Column\Column $column)
696
	{
697
		$this->onColumnAdd($key, $column);
0 ignored issues
show
Documentation Bug introduced by
The method onColumnAdd 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...
698
699
		return $this->columns[$key] = $column;
700
	}
701
702
703
	/**
704
	 * Return existing column
705
	 * @param  string $key
706
	 * @return Column\Column
707
	 * @throws DataGridException
708
	 */
709
	public function getColumn($key)
710
	{
711
		if (!isset($this->columns[$key])) {
712
			throw new DataGridException("There is no column at key [$key] defined.");
713
		}
714
715
		return $this->columns[$key];
716
	}
717
718
719
	/**
720
	 * Remove column
721
	 * @param string $key
722
	 * @return void
723
	 */
724
	public function removeColumn($key)
725
	{
726
		unset($this->columns[$key]);
727
	}
728
729
730
	/**
731
	 * Check whether given key already exists in $this->columns
732
	 * @param  string $key
733
	 * @throws DataGridException
734
	 */
735
	protected function addColumnCheck($key)
736
	{
737
		if (isset($this->columns[$key])) {
738
			throw new DataGridException("There is already column at key [$key] defined.");
739
		}
740
	}
741
742
743
	/********************************************************************************
744
	 *                                    ACTIONS                                   *
745
	 ********************************************************************************/
746
747
748
	/**
749
	 * Create action
750
	 * @param string     $key
751
	 * @param string     $name
752
	 * @param string     $href
753
	 * @param array|null $params
754
	 * @return Column\Action
755
	 */
756
	public function addAction($key, $name, $href = NULL, array $params = NULL)
757
	{
758
		$this->addActionCheck($key);
759
		$href = $href ?: $key;
760
761
		if (NULL === $params) {
762
			$params = [$this->primary_key];
763
		}
764
765
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
766
	}
767
768
769
	/**
770
	 * Create action callback
771
	 * @param string     $key
772
	 * @param string     $name
773
	 * @return Column\Action
774
	 */
775
	public function addActionCallback($key, $name, $callback = NULL)
776
	{
777
		$this->addActionCheck($key);
778
		$params = ['__id' => $this->primary_key];
779
780
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
781
782
		if ($callback) {
783
			if (!is_callable($callback)) {
784
				throw new DataGridException('ActionCallback callback has to be callable.');
785
			}
786
787
			$action->onClick[] = $callback;
788
		}
789
790
		return $action;
791
	}
792
793
794
	/**
795
	 * Get existing action
796
	 * @param  string       $key
797
	 * @return Column\Action
798
	 * @throws DataGridException
799
	 */
800
	public function getAction($key)
801
	{
802
		if (!isset($this->actions[$key])) {
803
			throw new DataGridException("There is no action at key [$key] defined.");
804
		}
805
806
		return $this->actions[$key];
807
	}
808
809
810
	/**
811
	 * Remove action
812
	 * @param string $key
813
	 * @return void
814
	 */
815
	public function removeAction($key)
816
	{
817
		unset($this->actions[$key]);
818
	}
819
820
821
	/**
822
	 * Check whether given key already exists in $this->filters
823
	 * @param  string $key
824
	 * @throws DataGridException
825
	 */
826
	protected function addActionCheck($key)
827
	{
828
		if (isset($this->actions[$key])) {
829
			throw new DataGridException("There is already action at key [$key] defined.");
830
		}
831
	}
832
833
834
	/********************************************************************************
835
	 *                                    FILTERS                                   *
836
	 ********************************************************************************/
837
838
839
	/**
840
	 * Add filter fot text search
841
	 * @param string       $key
842
	 * @param string       $name
843
	 * @param array|string $columns
844
	 * @return Filter\FilterText
845
	 * @throws DataGridException
846
	 */
847
	public function addFilterText($key, $name, $columns = NULL)
848
	{
849
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
850
851
		if (!is_array($columns)) {
852
			throw new DataGridException("Filter Text can except only array or string.");
853
		}
854
855
		$this->addFilterCheck($key);
856
857
		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...
858
	}
859
860
861
	/**
862
	 * Add select box filter
863
	 * @param string $key
864
	 * @param string $name
865
	 * @param array  $options
866
	 * @param string $column
867
	 * @return Filter\FilterSelect
868
	 * @throws DataGridException
869
	 */
870 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...
871
	{
872
		$column = $column ?: $key;
873
874
		if (!is_string($column)) {
875
			throw new DataGridException("Filter Select can only filter through one column.");
876
		}
877
878
		$this->addFilterCheck($key);
879
880
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
881
	}
882
883
884
	/**
885
	 * Add datepicker filter
886
	 * @param string $key
887
	 * @param string $name
888
	 * @param string $column
889
	 * @return Filter\FilterDate
890
	 * @throws DataGridException
891
	 */
892
	public function addFilterDate($key, $name, $column = NULL)
893
	{
894
		$column = $column ?: $key;
895
896
		if (!is_string($column)) {
897
			throw new DataGridException("FilterDate can only filter through one column.");
898
		}
899
900
		$this->addFilterCheck($key);
901
902
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
903
	}
904
905
906
	/**
907
	 * Add range filter (from - to)
908
	 * @param string $key
909
	 * @param string $name
910
	 * @param string $column
911
	 * @return Filter\FilterRange
912
	 * @throws DataGridException
913
	 */
914 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...
915
	{
916
		$column = $column ?: $key;
917
918
		if (!is_string($column)) {
919
			throw new DataGridException("FilterRange can only filter through one column.");
920
		}
921
922
		$this->addFilterCheck($key);
923
924
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
925
	}
926
927
928
	/**
929
	 * Add datepicker filter (from - to)
930
	 * @param string $key
931
	 * @param string $name
932
	 * @param string $column
933
	 * @return Filter\FilterDateRange
934
	 * @throws DataGridException
935
	 */
936 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...
937
	{
938
		$column = $column ?: $key;
939
940
		if (!is_string($column)) {
941
			throw new DataGridException("FilterDateRange can only filter through one column.");
942
		}
943
944
		$this->addFilterCheck($key);
945
946
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
947
	}
948
949
950
	/**
951
	 * Check whether given key already exists in $this->filters
952
	 * @param  string $key
953
	 * @throws DataGridException
954
	 */
955
	protected function addFilterCheck($key)
956
	{
957
		if (isset($this->filters[$key])) {
958
			throw new DataGridException("There is already action at key [$key] defined.");
959
		}
960
	}
961
962
963
	/**
964
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
965
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
966
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
967
	 */
968
	public function assableFilters()
969
	{
970
		foreach ($this->filter as $key => $value) {
971
			if (!isset($this->filters[$key])) {
972
				$this->deleteSesssionData($key);
973
974
				continue;
975
			}
976
977
			if (is_array($value) || $value instanceof \Traversable) {
978
				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...
979
					$this->filters[$key]->setValue($value);
980
				}
981
			} else {
982
				if ($value !== '' && $value !== NULL) {
983
					$this->filters[$key]->setValue($value);
984
				}
985
			}
986
		}
987
988
		foreach ($this->columns as $column) {
989
			if (isset($this->sort[$column->getColumnName()])) {
990
				$column->setSort($this->sort);
991
			}
992
		}
993
994
		return $this->filters;
995
	}
996
997
998
	/**
999
	 * Remove filter
1000
	 * @param string $key
1001
	 * @return void
1002
	 */
1003
	public function removeFilter($key)
1004
	{
1005
		unset($this->filters[$key]);
1006
	}
1007
1008
1009
	/********************************************************************************
1010
	 *                                  FILTERING                                   *
1011
	 ********************************************************************************/
1012
1013
1014
	/**
1015
	 * Is filter active?
1016
	 * @return boolean
1017
	 */
1018
	public function isFilterActive()
1019
	{
1020
		$is_filter = ArraysHelper::testTruthy($this->filter);
1021
1022
		return ($is_filter) || $this->force_filter_active;
1023
	}
1024
1025
1026
	/**
1027
	 * Tell that filter is active from whatever reasons
1028
	 * return static
1029
	 */
1030
	public function setFilterActive()
1031
	{
1032
		$this->force_filter_active = TRUE;
1033
1034
		return $this;
1035
	}
1036
1037
1038
	/**
1039
	 * If we want to sent some initial filter
1040
	 * @param array $filter
1041
	 * @return static
1042
	 */
1043
	public function setFilter(array $filter)
1044
	{
1045
		$this->filter = $filter;
1046
1047
		return $this;
1048
	}
1049
1050
1051
	/**
1052
	 * FilterAndGroupAction form factory
1053
	 * @return Form
1054
	 */
1055
	public function createComponentFilter()
1056
	{
1057
		$form = new Form($this, 'filter');
1058
1059
		$form->setTranslator($this->getTranslator());
1060
1061
		$form->setMethod('get');
1062
1063
		/**
1064
		 * Filter part
1065
		 */
1066
		$filter_container = $form->addContainer('filter');
1067
1068
		foreach ($this->filters as $filter) {
1069
			$filter->addToFormContainer($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...
1070
		}
1071
1072
		/**
1073
		 * Group action part
1074
		 */
1075
		$group_action_container = $form->addContainer('group_action');
1076
1077
		if ($this->hasGroupActions()) {
1078
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1079
		}
1080
1081
		$form->setDefaults(['filter' => $this->filter]);
1082
1083
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1084
1085
		return $form;
1086
	}
1087
1088
1089
	/**
1090
	 * Set $this->filter values after filter form submitted
1091
	 * @param  Form $form
1092
	 * @return void
1093
	 */
1094
	public function filterSucceeded(Form $form)
1095
	{
1096
		$values = $form->getValues();
1097
1098
		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...
1099
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1100
				return;
1101
			}
1102
		}
1103
1104
		$values = $values['filter'];
1105
1106
		foreach ($values as $key => $value) {
1107
			/**
1108
			 * Session stuff
1109
			 */
1110
			$this->saveSessionData($key, $value);
1111
1112
			/**
1113
			 * Other stuff
1114
			 */
1115
			$this->filter[$key] = $value;
1116
		}
1117
1118
		$this->reload();
1119
	}
1120
1121
1122
	/**
1123
	 * Should be datagrid filters rendered separately?
1124
	 * @param boolean $out
1125
	 * @return static
1126
	 */
1127
	public function setOuterFilterRendering($out = TRUE)
1128
	{
1129
		$this->outer_filter_rendering = (bool) $out;
1130
1131
		return $this;
1132
	}
1133
1134
1135
	/**
1136
	 * Are datagrid filters rendered separately?
1137
	 * @return boolean
1138
	 */
1139
	public function hasOuterFilterRendering()
1140
	{
1141
		return $this->outer_filter_rendering;
1142
	}
1143
1144
1145
	/**
1146
	 * Try to restore session stuff
1147
	 * @return void
1148
	 */
1149
	public function findSessionFilters()
1150
	{
1151
		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...
1152
			return;
1153
		}
1154
1155
		if (!$this->remember_state) {
1156
			return;
1157
		}
1158
1159
		if ($page = $this->getSessionData('_grid_page')) {
1160
			$this->page = $page;
1161
		}
1162
1163
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1164
			$this->per_page = $per_page;
1165
		}
1166
1167
		if ($sort = $this->getSessionData('_grid_sort')) {
1168
			$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...
1169
		}
1170
1171
		foreach ($this->getSessionData() as $key => $value) {
1172
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page', '_grid_hidden_columns'])) {
1173
				$this->filter[$key] = $value;
1174
			}
1175
		}
1176
	}
1177
1178
1179
	/********************************************************************************
1180
	 *                                    EXPORTS                                   *
1181
	 ********************************************************************************/
1182
1183
1184
	/**
1185
	 * Add export of type callback
1186
	 * @param string $text
1187
	 * @param callable $callback
1188
	 * @param boolean $filtered
1189
	 * @return Export\Export
1190
	 */
1191
	public function addExportCallback($text, $callback, $filtered = FALSE)
1192
	{
1193
		if (!is_callable($callback)) {
1194
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1195
		}
1196
1197
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
1198
	}
1199
1200
1201
	/**
1202
	 * Add already implemented csv export
1203
	 * @param string $text
1204
	 * @param string $csv_file_name
1205
	 * @return Export\Export
1206
	 */
1207
	public function addExportCsv($text, $csv_file_name)
1208
	{
1209
		return $this->addToExports(new Export\ExportCsv($text, $csv_file_name, FALSE));
1210
	}
1211
1212
1213
	/**
1214
	 * Add already implemented csv export, but for filtered data
1215
	 * @param string $text
1216
	 * @param string $csv_file_name
1217
	 * @return Export\Export
1218
	 */
1219
	public function addExportCsvFiltered($text, $csv_file_name)
1220
	{
1221
		return $this->addToExports(new Export\ExportCsv($text, $csv_file_name, TRUE));
1222
	}
1223
1224
1225
	/**
1226
	 * Add export to array
1227
	 * @param Export\Export $export
1228
	 * @return Export\Export
1229
	 */
1230
	protected function addToExports(Export\Export $export)
1231
	{
1232
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1233
1234
		$export->setLink($this->link('export!', ['id' => $id]));
1235
1236
		return $this->exports[$id] = $export;
1237
	}
1238
1239
1240
	public function resetExportsLinks()
1241
	{
1242
		foreach ($this->exports as $id => $export) {
1243
			$export->setLink($this->link('export!', ['id' => $id]));
1244
		}
1245
	}
1246
1247
1248
	/********************************************************************************
1249
	 *                                 GROUP ACTIONS                                *
1250
	 ********************************************************************************/
1251
1252
1253
	/**
1254
	 * Add group actino
1255
	 * @param string $title
1256
	 * @param array  $options
1257
	 * @return GroupAction\GroupAction
1258
	 */
1259
	public function addGroupAction($title, $options = [])
1260
	{
1261
		return $this->getGroupActionCollection()->addGroupAction($title, $options);
1262
	}
1263
1264
1265
	/**
1266
	 * Get collection of all group actions
1267
	 * @return GroupAction\GroupActionCollection
1268
	 */
1269
	public function getGroupActionCollection()
1270
	{
1271
		if (!$this->group_action_collection) {
1272
			$this->group_action_collection = new GroupAction\GroupActionCollection();
1273
		}
1274
1275
		return $this->group_action_collection;
1276
	}
1277
1278
1279
	/**
1280
	 * Has datagrid some group actions?
1281
	 * @return boolean
1282
	 */
1283
	public function hasGroupActions()
1284
	{
1285
		return (bool) $this->group_action_collection;
1286
	}
1287
1288
1289
	/********************************************************************************
1290
	 *                                   HANDLERS                                   *
1291
	 ********************************************************************************/
1292
1293
1294
	/**
1295
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1296
	 * @param  int  $page
1297
	 * @return void
1298
	 */
1299
	public function handlePage($page)
1300
	{
1301
		/**
1302
		 * Session stuff
1303
		 */
1304
		$this->page = $page;
1305
		$this->saveSessionData('_grid_page', $page);
1306
1307
		$this->reload(['table']);
1308
	}
1309
1310
1311
	/**
1312
	 * Handler for sorting
1313
	 * @param array $sort
1314
	 * @return void
1315
	 */
1316
	public function handleSort(array $sort)
1317
	{
1318
		/**
1319
		 * Session stuff
1320
		 */
1321
		$this->sort = $sort;
1322
		$this->saveSessionData('_grid_sort', $this->sort);
1323
1324
		$this->reload(['table']);
1325
	}
1326
1327
1328
	/**
1329
	 * handler for reseting the filter
1330
	 * @return void
1331
	 */
1332
	public function handleResetFilter()
1333
	{
1334
		/**
1335
		 * Session stuff
1336
		 */
1337
		$this->deleteSesssionData('_grid_page');
1338
1339
		foreach ($this->getSessionData() as $key => $value) {
1340
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page'])) {
1341
				$this->deleteSesssionData($key);
1342
			}
1343
		}
1344
1345
		$this->filter = [];
1346
1347
		$this->reload(['grid']);
1348
	}
1349
1350
1351
	/**
1352
	 * Handler for export
1353
	 * @param  int $id Key for particular export class in array $this->exports
1354
	 * @return void
1355
	 */
1356
	public function handleExport($id)
1357
	{
1358
		if (!isset($this->exports[$id])) {
1359
			throw new Nette\Application\ForbiddenRequestException;
1360
		}
1361
1362
		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...
1363
			$this->setColumnsOrder($this->columns_export_order);
1364
		}
1365
1366
		$export = $this->exports[$id];
1367
1368
		if ($export->isFiltered()) {
1369
			$sort      = $this->sort;
1370
			$filter    = $this->assableFilters();
1371
		} else {
1372
			$sort      = [$this->primary_key => 'ASC'];
1373
			$filter    = [];
1374
		}
1375
1376
		if (NULL === $this->dataModel) {
1377
			throw new DataGridException('You have to set a data source first.');
1378
		}
1379
1380
		$rows = [];
1381
1382
		$items = Nette\Utils\Callback::invokeArgs(
1383
			[$this->dataModel, 'filterData'], [NULL, $sort, $filter]
1384
		);
1385
1386
		foreach ($items as $item) {
1387
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1388
		}
1389
1390
		if ($export instanceof Export\ExportCsv) {
1391
			$export->invoke($rows, $this);
1392
		} else {
1393
			$export->invoke($items, $this);
1394
		}
1395
1396
		if ($export->isAjax()) {
1397
			$this->reload();
1398
		}
1399
	}
1400
1401
1402
	/**
1403
	 * Handler for getting children of parent item (e.g. category)
1404
	 * @param  int $parent
1405
	 * @return void
1406
	 */
1407
	public function handleGetChildren($parent)
1408
	{
1409
		$this->setDataSource(
1410
			call_user_func($this->tree_view_children_callback, $parent)
1411
		);
1412
1413
		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...
1414
			$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...
1415
			$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...
1416
1417
			$this->redrawControl('items');
1418
1419
			$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...
1420
		} else {
1421
			$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...
1422
		}
1423
	}
1424
1425
1426
	/**
1427
	 * Handler for getting item detail
1428
	 * @param  mixed $id
1429
	 * @return void
1430
	 */
1431
	public function handleGetItemDetail($id)
1432
	{
1433
		$this->template->add('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...
1434
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1435
1436
		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...
1437
			$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...
1438
			$this->redrawControl('items');
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
		} else {
1442
			$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...
1443
		}
1444
	}
1445
1446
1447
	/**
1448
	 * Handler for inline editing
1449
	 * @param  mixed $id
1450
	 * @param  mixed $key
1451
	 * @return void
1452
	 */
1453
	public function handleEdit($id, $key)
1454
	{
1455
		$column = $this->getColumn($key);
1456
		$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...
1457
1458
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1459
	}
1460
1461
1462
	/**
1463
	 * Redraw $this
1464
	 * @return void
1465
	 */
1466
	public function reload($snippets = [])
1467
	{
1468
		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...
1469
			$this->redrawControl('tbody');
1470
			$this->redrawControl('pagination');
1471
1472
			/**
1473
			 * manualy reset exports links...
1474
			 */
1475
			$this->resetExportsLinks();
1476
			$this->redrawControl('exports');
1477
1478
			foreach ($snippets as $snippet) {
1479
				$this->redrawControl($snippet);
1480
			}
1481
1482
			$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...
1483
1484
			$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...
1485
		} else {
1486
			$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...
1487
		}
1488
	}
1489
1490
1491
	/**
1492
	 * Handler for column status
1493
	 * @param  string $id
1494
	 * @param  string $key
1495
	 * @param  string $value
1496
	 * @return void
1497
	 */
1498
	public function handleChangeStatus($id, $key, $value)
1499
	{
1500
		if (empty($this->columns[$key])) {
1501
			throw new DataGridException("ColumnStatus[$key] does not exist");
1502
		}
1503
1504
		$this->columns[$key]->onChange($id, $value);
1505
1506
		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...
1507
			$this->redrawItem($id);
1508
		}
1509
	}
1510
1511
1512
	/**
1513
	 * Redraw just one row via ajax
1514
	 * @param  int   $id
1515
	 * @param  mixed $primary_where_column
1516
	 * @return void
1517
	 */
1518
	public function redrawItem($id, $primary_where_column = NULL)
1519
	{
1520
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1521
1522
		$this->redrawControl('items');
1523
		$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...
1524
1525
		$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...
1526
	}
1527
1528
1529
	/**
1530
	 * Tell datagrid to display all columns
1531
	 * @return void
1532
	 */
1533
	public function handleShowAllColumns()
1534
	{
1535
		$this->deleteSesssionData('_grid_hidden_columns');
1536
1537
		$this->redrawControl();
1538
1539
		$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...
1540
	}
1541
1542
1543
	/**
1544
	 * Notice datagrid to not display particular columns
1545
	 * @param  string $column
1546
	 * @return void
1547
	 */
1548
	public function handleHideColumn($column)
1549
	{
1550
		/**
1551
		 * Store info about hiding a column to session
1552
		 */
1553
		$columns = $this->getSessionData('_grid_hidden_columns');
1554
1555
		if (empty($columns)) {
1556
			$columns = [$column];
1557
		} else if (!in_array($column, $columns)) {
1558
			array_push($columns, $column);
1559
		}
1560
1561
		$this->saveSessionData('_grid_hidden_columns', $columns);
1562
1563
		$this->redrawControl();
1564
1565
		$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...
1566
	}
1567
1568
1569
	public function handleActionCallback($__key, $__id)
1570
	{
1571
		$action = $this->getAction($__key);
1572
1573
		if (!($action instanceof Column\ActionCallback)) {
1574
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
1575
		}
1576
1577
		$action->onClick($__id);
1578
	}
1579
1580
1581
	/********************************************************************************
1582
	 *                                  PAGINATION                                  *
1583
	 ********************************************************************************/
1584
1585
1586
	/**
1587
	 * Set options of select "items_per_page"
1588
	 * @param array $items_per_page_list
1589
	 * @return static
1590
	 */
1591
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
1592
	{
1593
		$this->items_per_page_list = $items_per_page_list;
1594
1595
		if ($include_all) {
1596
			$this->items_per_page_list[] = 'all';
1597
		}
1598
1599
		return $this;
1600
	}
1601
1602
1603
	/**
1604
	 * Paginator factory
1605
	 * @return Components\DataGridPaginator\DataGridPaginator
1606
	 */
1607
	public function createComponentPaginator()
1608
	{
1609
		/**
1610
		 * Init paginator
1611
		 */
1612
		$component = new Components\DataGridPaginator\DataGridPaginator(
1613
			$this->getTranslator()
1614
		);
1615
		$paginator = $component->getPaginator();
1616
1617
		$paginator->setPage($this->page);
1618
		$paginator->setItemsPerPage($this->getPerPage());
1619
1620
		return $component;
1621
	}
1622
1623
1624
	/**
1625
	 * PerPage form factory
1626
	 * @return Form
1627
	 */
1628
	public function createComponentPerPage()
1629
	{
1630
		$form = new Form;
1631
1632
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1633
			->setValue($this->getPerPage());
1634
1635
		$form->addSubmit('submit', '');
1636
1637
		$saveSessionData = [$this, 'saveSessionData'];
1638
1639
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
1640
			/**
1641
			 * Session stuff
1642
			 */
1643
			$saveSessionData('_grid_per_page', $values->per_page);
1644
1645
			/**
1646
			 * Other stuff
1647
			 */
1648
			$this->per_page = $values->per_page;
1649
			$this->reload();
1650
		};
1651
1652
		return $form;
1653
	}
1654
1655
1656
	/**
1657
	 * Get parameter per_page
1658
	 * @return int
1659
	 */
1660
	public function getPerPage()
1661
	{
1662
		$items_per_page_list = $this->getItemsPerPageList();
1663
1664
		$per_page = $this->per_page ?: reset($items_per_page_list);
1665
1666
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
1667
			$per_page = reset($items_per_page_list);
1668
		}
1669
1670
		return $per_page;
1671
	}
1672
1673
1674
	/**
1675
	 * Get associative array of items_per_page_list
1676
	 * @return array
1677
	 */
1678
	public function getItemsPerPageList()
1679
	{
1680
		if (!$this->items_per_page_list) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->items_per_page_list 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...
1681
			$this->setItemsPerPageList([10, 20, 50], TRUE);
1682
		}
1683
1684
		$list = array_flip($this->items_per_page_list);
1685
1686
		foreach ($list as $key => $value) {
1687
			$list[$key] = $key;
1688
		}
1689
1690
		if (array_key_exists('all', $list)) {
1691
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
1692
		}
1693
1694
		return $list;
1695
	}
1696
1697
1698
	/**
1699
	 * Order Grid to "be paginated"
1700
	 * @param bool $do
1701
	 * @return static
1702
	 */
1703
	public function setPagination($do)
1704
	{
1705
		$this->do_paginate = (bool) $do;
1706
1707
		return $this;
1708
	}
1709
1710
1711
	/**
1712
	 * Tell whether Grid is paginated
1713
	 * @return bool
1714
	 */
1715
	public function isPaginated()
1716
	{
1717
		return $this->do_paginate;
1718
	}
1719
1720
1721
	/**
1722
	 * Return current paginator class
1723
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
1724
	 */
1725
	public function getPaginator()
1726
	{
1727
		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...
1728
			return $this['paginator'];
1729
		}
1730
1731
		return NULL;
1732
	}
1733
1734
1735
	/********************************************************************************
1736
	 *                                     I18N                                     *
1737
	 ********************************************************************************/
1738
1739
1740
	/**
1741
	 * Set datagrid translator
1742
	 * @param Nette\Localization\ITranslator $translator
1743
	 * @return static
1744
	 */
1745
	public function setTranslator(Nette\Localization\ITranslator $translator)
1746
	{
1747
		$this->translator = $translator;
1748
1749
		return $this;
1750
	}
1751
1752
1753
	/**
1754
	 * Get translator for datagrid
1755
	 * @return Nette\Localization\ITranslator
1756
	 */
1757
	public function getTranslator()
1758
	{
1759
		if (!$this->translator) {
1760
			$this->translator = new Localization\SimpleTranslator;
1761
		}
1762
1763
		return $this->translator;
1764
	}
1765
1766
1767
	/********************************************************************************
1768
	 *                                 COLUMNS ORDER                                *
1769
	 ********************************************************************************/
1770
1771
1772
	/**
1773
	 * Set order of datagrid columns
1774
	 * @param array $order
1775
	 * @return static
1776
	 */
1777
	public function setColumnsOrder($order)
1778
	{
1779
		$new_order = [];
1780
1781
		foreach ($order as $key) {
1782
			if (isset($this->columns[$key])) {
1783
				$new_order[$key] = $this->columns[$key];
1784
			}
1785
		}
1786
1787
		if (sizeof($new_order) === sizeof($this->columns)) {
1788
			$this->columns = $new_order;
1789
		} else {
1790
			throw new DataGridException('When changing columns order, you have to specify all columns');
1791
		}
1792
1793
		return $this;
1794
	}
1795
1796
1797
	/**
1798
	 * Columns order may be different for export and normal grid
1799
	 * @param array $order
1800
	 */
1801
	public function setColumnsExportOrder($order)
1802
	{
1803
		$this->columns_export_order = (array) $order;
1804
	}
1805
1806
1807
	/********************************************************************************
1808
	 *                                SESSION & URL                                 *
1809
	 ********************************************************************************/
1810
1811
1812
	/**
1813
	 * Find some unique session key name
1814
	 * @return string
1815
	 */
1816
	public function getSessionSectionName()
1817
	{
1818
		return $this->getPresenter()->getName().':'.$this->getName();
1819
	}
1820
1821
1822
	/**
1823
	 * Should datagrid remember its filters/pagination/etc using session?
1824
	 * @param bool $remember
1825
	 * @return static
1826
	 */
1827
	public function setRememberState($remember = TRUE)
1828
	{
1829
		$this->remember_state = (bool) $remember;
1830
1831
		return $this;
1832
	}
1833
1834
1835
	/**
1836
	 * Should datagrid refresh url using history API?
1837
	 * @param bool $refresh
1838
	 * @return static
1839
	 */
1840
	public function setRefreshUrl($refresh = TRUE)
1841
	{
1842
		$this->refresh_url = (bool) $refresh;
1843
1844
1845
		return $this;
1846
	}
1847
1848
1849
	/**
1850
	 * Get session data if functionality is enabled
1851
	 * @param  string $key
1852
	 * @return mixed
1853
	 */
1854
	public function getSessionData($key = NULL, $default_value = NULL)
1855
	{
1856
		if (!$this->remember_state) {
1857
			return $key ? $default_value : [];
1858
		}
1859
1860
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
1861
	}
1862
1863
1864
	/**
1865
	 * Save session data - just if it is enabled
1866
	 * @param  string $key
1867
	 * @param  mixed  $value
1868
	 * @return void
1869
	 */
1870
	public function saveSessionData($key, $value)
1871
	{
1872
		if ($this->remember_state) {
1873
			$this->grid_session->{$key} = $value;
1874
		}
1875
	}
1876
1877
1878
	/**
1879
	 * Delete session data
1880
	 * @return void
1881
	 */
1882
	public function deleteSesssionData($key)
1883
	{
1884
		unset($this->grid_session->{$key});
1885
	}
1886
1887
1888
	/********************************************************************************
1889
	 *                                  ITEM DETAIL                                 *
1890
	 ********************************************************************************/
1891
1892
1893
	/**
1894
	 * Get items detail parameters
1895
	 * @return array
1896
	 */
1897
	public function getItemsDetail()
1898
	{
1899
		return $this->items_detail;
1900
	}
1901
1902
1903
	/**
1904
	 * Items can have thair detail - toggled
1905
	 * @param mixed $detail callable|string|bool
1906
	 * @param bool|NULL $primary_where_column
1907
	 * @return Column\ItemDetail
1908
	 */
1909
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
1910
	{
1911
		if ($this->isSortable()) {
1912
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
1913
		}
1914
1915
		$this->items_detail = new Column\ItemDetail(
1916
			$this,
1917
			$primary_where_column ?: $this->primary_key
0 ignored issues
show
Bug introduced by
It seems like $primary_where_column ?: $this->primary_key can also be of type boolean; however, Ublaboo\DataGrid\Column\ItemDetail::__construct() does only seem to accept string, 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...
1918
		);
1919
1920
		if (is_string($detail)) {
1921
			/**
1922
			 * Item detail will be in separate template
1923
			 */
1924
			$this->items_detail->setType('template');
1925
			$this->items_detail->setTemplate($detail);
1926
1927
		} else if (is_callable($detail)) {
1928
			/**
1929
			 * Item detail will be rendered via custom callback renderer
1930
			 */
1931
			$this->items_detail->setType('renderer');
1932
			$this->items_detail->setRenderer($detail);
1933
1934
		} else if (TRUE === $detail) {
1935
			/**
1936
			 * Item detail will be rendered probably via block #detail
1937
			 */
1938
			$this->items_detail->setType('block');
1939
1940
		} else {
1941
			throw new DataGridException(
1942
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
1943
			);
1944
		}
1945
1946
		return $this->items_detail;
1947
	}
1948
1949
1950
	/********************************************************************************
1951
	 *                                ROW PRIVILEGES                                *
1952
	 ********************************************************************************/
1953
1954
1955
	public function allowRowsGroupAction(callable $condition)
1956
	{
1957
		$this->row_conditions['group_action'] = $condition;
1958
	}
1959
1960
1961
	public function allowRowsAction($key, callable $condition)
1962
	{
1963
		$this->row_conditions['action'][$key] = $condition;
1964
	}
1965
1966
1967
	public function getRowCondition($name, $key = NULL)
1968
	{
1969
		if (!isset($this->row_conditions[$name])) {
1970
			return FALSE;
1971
		}
1972
1973
		$condition = $this->row_conditions[$name];
1974
1975
		if (!$key) {
1976
			return $condition;
1977
		}
1978
1979
		return isset($condition[$key]) ? $condition[$key] : FALSE;
1980
	}
1981
1982
1983
	/********************************************************************************
1984
	 *                               HIDEABLE COLUMNS                               *
1985
	 ********************************************************************************/
1986
1987
1988
	/**
1989
	 * Can datagrid hide colums?
1990
	 * @return boolean
1991
	 */
1992
	public function canHideColumns()
1993
	{
1994
		return (bool) $this->can_hide_columns;
1995
	}
1996
1997
1998
	/**
1999
	 * Order Grid to set columns hideable.
2000
	 * @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...
2001
	 * @return static
2002
	 */
2003
	public function setColumnsHideable()
2004
	{
2005
		$this->can_hide_columns = TRUE;
2006
2007
		return $this;
2008
	}
2009
2010
2011
	/********************************************************************************
2012
	 *                                   INTERNAL                                   *
2013
	 ********************************************************************************/
2014
2015
2016
	/**
2017
	 * Get cont of columns
2018
	 * @return int
2019
	 */
2020
	public function getColumnsCount()
2021
	{
2022
		$count = sizeof($this->getColumns());
2023
2024
		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...
2025
			$count++;
2026
		}
2027
2028
		if ($this->hasGroupActions()) {
2029
			$count++;
2030
		}
2031
2032
		return $count;
2033
	}
2034
2035
2036
	/**
2037
	 * Get primary key of datagrid data source
2038
	 * @return string
2039
	 */
2040
	public function getPrimaryKey()
2041
	{
2042
		return $this->primary_key;
2043
	}
2044
2045
2046
	/**
2047
	 * Get set of set columns
2048
	 * @return Column\IColumn[]
2049
	 */
2050
	public function getColumns()
2051
	{
2052
		foreach ($this->getSessionData('_grid_hidden_columns', []) as $column) {
2053
			$this->removeColumn($column);
2054
		}
2055
2056
		return $this->columns;
2057
	}
2058
2059
2060
	/**
2061
	 * @return PresenterComponent
2062
	 */
2063
	public function getParent()
2064
	{
2065
		$parent = parent::getParent();
2066
2067
		if (!($parent instanceof PresenterComponent)) {
2068
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2069
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2070
			);
2071
		}
2072
2073
		return $parent;
2074
	}
2075
2076
}
2077