Completed
Push — master ( 9c2e1e...c9039e )
by Pavel
02:44
created

DataGrid::getColumns()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 3
Metric Value
c 4
b 0
f 3
dl 0
loc 32
rs 6.7272
cc 7
eloc 17
nc 9
nop 0
1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid;
10
11
use Nette;
12
use 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
use Ublaboo\DataGrid\Utils\Sorting;
18
use Ublaboo\DataGrid\InlineEdit\InlineEdit;
19
20
/**
21
 * @method onRedraw()
22
 */
23
class DataGrid extends Nette\Application\UI\Control
24
{
25
26
	/**
27
	 * @var callable[]
28
	 */
29
	public $onRedraw;
30
31
	/**
32
	 * @var string
33
	 */
34
	public static $icon_prefix = 'fa fa-';
35
36
	/**
37
	 * When set to TRUE, datagrid throws an exception
38
	 * 	when tring to get related entity within join and entity does not exist
39
	 * @var bool
40
	 */
41
	public $strict_entity_property = FALSE;
42
43
	/**
44
	 * @var int
45
	 * @persistent
46
	 */
47
	public $page = 1;
48
49
	/**
50
	 * @var int|string
51
	 * @persistent
52
	 */
53
	public $per_page;
54
55
	/**
56
	 * @var array
57
	 * @persistent
58
	 */
59
	public $sort = [];
60
61
	/**
62
	 * @var array
63
	 */
64
	public $default_sort = [];
65
66
	/**
67
	 * @var array
68
	 * @persistent
69
	 */
70
	public $filter = [];
71
72
	/**
73
	 * @var Callable[]
74
	 */
75
	public $onRender = [];
76
77
	/**
78
	 * @var callable[]
79
	 */
80
	public $onColumnAdd;
81
82
	/**
83
	 * @var callable|null
84
	 */
85
	protected $sort_callback = NULL;
86
87
	/**
88
	 * @var bool
89
	 */
90
	protected $use_happy_components = TRUE;
91
92
	/**
93
	 * @var callable
94
	 */
95
	protected $rowCallback;
96
97
	/**
98
	 * @var array
99
	 */
100
	protected $items_per_page_list;
101
102
	/**
103
	 * @var string
104
	 */
105
	protected $template_file;
106
107
	/**
108
	 * @var Column\IColumn[]
109
	 */
110
	protected $columns = [];
111
112
	/**
113
	 * @var Column\Action[]
114
	 */
115
	protected $actions = [];
116
117
	/**
118
	 * @var GroupAction\GroupActionCollection
119
	 */
120
	protected $group_action_collection;
121
122
	/**
123
	 * @var Filter\Filter[]
124
	 */
125
	protected $filters = [];
126
127
	/**
128
	 * @var Export\Export[]
129
	 */
130
	protected $exports = [];
131
132
	/**
133
	 * @var DataModel
134
	 */
135
	protected $dataModel;
136
137
	/**
138
	 * @var DataFilter
139
	 */
140
	protected $dataFilter;
141
142
	/**
143
	 * @var string
144
	 */
145
	protected $primary_key = 'id';
146
147
	/**
148
	 * @var bool
149
	 */
150
	protected $do_paginate = TRUE;
151
152
	/**
153
	 * @var bool
154
	 */
155
	protected $csv_export = TRUE;
156
157
	/**
158
	 * @var bool
159
	 */
160
	protected $csv_export_filtered = TRUE;
161
162
	/**
163
	 * @var bool
164
	 */
165
	protected $sortable = FALSE;
166
167
	/**
168
	 * @var string
169
	 */
170
	protected $sortable_handler = 'sort!';
171
172
	/**
173
	 * @var string
174
	 */
175
	protected $original_template;
176
177
	/**
178
	 * @var array
179
	 */
180
	protected $redraw_item;
181
182
	/**
183
	 * @var mixed
184
	 */
185
	protected $translator;
186
187
	/**
188
	 * @var bool
189
	 */
190
	protected $force_filter_active;
191
192
	/**
193
	 * @var callable
194
	 */
195
	protected $tree_view_children_callback;
196
197
	/**
198
	 * @var callable
199
	 */
200
	protected $tree_view_has_children_callback;
201
202
	/**
203
	 * @var string
204
	 */
205
	protected $tree_view_has_children_column;
206
207
	/**
208
	 * @var bool
209
	 */
210
	protected $outer_filter_rendering = FALSE;
211
212
	/**
213
	 * @var array
214
	 */
215
	protected $columns_export_order = [];
216
217
	/**
218
	 * @var bool
219
	 */
220
	protected $remember_state = TRUE;
221
222
	/**
223
	 * @var bool
224
	 */
225
	protected $refresh_url = TRUE;
226
227
	/**
228
	 * @var Nette\Http\SessionSection
229
	 */
230
	protected $grid_session;
231
232
	/**
233
	 * @var Column\ItemDetail
234
	 */
235
	protected $items_detail;
236
237
	/**
238
	 * @var array
239
	 */
240
	protected $row_conditions = [
241
		'group_action' => FALSE,
242
		'action' => []
243
	];
244
245
	/**
246
	 * @var array
247
	 */
248
	protected $column_callbacks = [];
249
250
	/**
251
	 * @var bool
252
	 */
253
	protected $can_hide_columns = FALSE;
254
255
	/**
256
	 * @var array
257
	 */
258
	protected $columns_visibility = [];
259
260
	/**
261
	 * @var InlineEdit
262
	 */
263
	protected $inlineEdit;
264
265
266
	/**
267
	 * @param Nette\ComponentModel\IContainer|NULL $parent
268
	 * @param string                               $name
269
	 */
270
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
271
	{
272
		parent::__construct($parent, $name);
273
274
		$this->monitor('Nette\Application\UI\Presenter');
275
	}
276
277
278
	/**
279
	 * {inheritDoc}
280
	 * @return void
281
	 */
282
	public function attached($presenter)
283
	{
284
		parent::attached($presenter);
285
286
		if ($presenter instanceof Nette\Application\UI\Presenter) {
287
			/**
288
			 * Get session
289
			 */
290
			if ($this->remember_state) {
291
				$this->grid_session = $presenter->getSession($this->getSessionSectionName());
0 ignored issues
show
Documentation Bug introduced by
It seems like $presenter->getSession($...etSessionSectionName()) can also be of type object<Nette\Http\Session>. However, the property $grid_session is declared as type object<Nette\Http\SessionSection>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
292
293
				/**
294
				 * Try to find previous filters/pagination/sort in session
295
				 */
296
				$this->findSessionFilters();
297
				$this->findDefaultSort();
298
			}
299
		}
300
	}
301
302
303
	/********************************************************************************
304
	 *                                  RENDERING                                   *
305
	 ********************************************************************************/
306
307
308
	/**
309
	 * Render template
310
	 * @return void
311
	 */
312
	public function render()
313
	{
314
		/**
315
		 * Check whether datagrid has set some columns, initiated data source, etc
316
		 */
317
		if (!($this->dataModel instanceof DataModel)) {
318
			throw new DataGridException('You have to set a data source first.');
319
		}
320
321
		if (empty($this->columns)) {
322
			throw new DataGridException('You have to add at least one column.');
323
		}
324
325
		$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...
326
327
		/**
328
		 * Invoke some possible events
329
		 */
330
		$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...
331
332
		/**
333
		 * Prepare data for rendering (datagrid may render just one item)
334
		 */
335
		$rows = [];
336
337
		if (!empty($this->redraw_item)) {
338
			$items = $this->dataModel->filterRow($this->redraw_item);
339
		} else {
340
			$items = Nette\Utils\Callback::invokeArgs(
341
				[$this->dataModel, 'filterData'],
342
				[
343
					$this->getPaginator(),
344
					new Sorting($this->sort, $this->sort_callback),
345
					$this->assableFilters()
346
				]
347
			);
348
		}
349
350
		$callback = $this->rowCallback ?: NULL;
351
352
		foreach ($items as $item) {
353
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
354
355
			if ($callback) {
356
				$callback($item, $row->getControl());
357
			}
358
		}
359
360
		if ($this->isTreeView()) {
361
			$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...
362
		}
363
364
		$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...
365
366
		$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...
367
		$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...
368
		$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...
369
		$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...
370
371
		$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...
372
		$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...
373
		$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...
374
		$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...
375
		$this->template->add('columns_visibility', $this->columns_visibility);
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...
376
377
		$this->template->add('inlineEdit', $this->inlineEdit);
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...
378
379
		/**
380
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
381
		 */
382
		$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...
383
384
		/**
385
		 * Set template file and render it
386
		 */
387
		$this->template->setFile($this->getTemplateFile());
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...
388
		$this->template->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...
389
	}
390
391
392
	/********************************************************************************
393
	 *                                 ROW CALLBACK                                 *
394
	 ********************************************************************************/
395
396
397
	/**
398
	 * Each row can be modified with user callback
399
	 * @param  callable  $callback
400
	 * @return static
401
	 */
402
	public function setRowCallback(callable $callback)
403
	{
404
		$this->rowCallback = $callback;
405
406
		return $this;
407
	}
408
409
410
	/********************************************************************************
411
	 *                                 DATA SOURCE                                  *
412
	 ********************************************************************************/
413
414
415
	/**
416
	 * By default ID, you can change that
417
	 * @param string $primary_key
418
	 * @return static
419
	 */
420
	public function setPrimaryKey($primary_key)
421
	{
422
		if ($this->dataModel instanceof DataModel) {
423
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
424
		}
425
426
		$this->primary_key = $primary_key;
427
428
		return $this;
429
	}
430
431
432
	/**
433
	 * Set Grid data source
434
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
435
	 * @return static
436
	 */
437
	public function setDataSource($source)
438
	{
439
		$this->dataModel = new DataModel($source, $this->primary_key);
440
441
		return $this;
442
	}
443
444
445
	/********************************************************************************
446
	 *                                  TEMPLATING                                  *
447
	 ********************************************************************************/
448
449
450
	/**
451
	 * Set custom template file to render
452
	 * @param string $template_file
453
	 * @return static
454
	 */
455
	public function setTemplateFile($template_file)
456
	{
457
		$this->template_file = $template_file;
458
459
		return $this;
460
	}
461
462
463
	/**
464
	 * Get DataGrid template file
465
	 * @return string
466
	 * @return static
467
	 */
468
	public function getTemplateFile()
469
	{
470
		return $this->template_file ?: $this->getOriginalTemplateFile();
471
	}
472
473
474
	/**
475
	 * Get DataGrid original template file
476
	 * @return string
477
	 */
478
	public function getOriginalTemplateFile()
479
	{
480
		return __DIR__.'/templates/datagrid.latte';
481
	}
482
483
484
	/**
485
	 * Tell datagrid wheteher to use or not happy components
486
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
487
	 * @return void|bool
488
	 */
489
	public function useHappyComponents($use = NULL)
490
	{
491
		if (NULL === $use) {
492
			return $this->use_happy_components;
493
		}
494
495
		$this->use_happy_components = (bool) $use;
496
	}
497
498
499
	/********************************************************************************
500
	 *                                   SORTING                                    *
501
	 ********************************************************************************/
502
503
504
	/**
505
	 * Set default sorting
506
	 * @param array $sort
507
	 * @return static
508
	 */
509
	public function setDefaultSort($sort)
510
	{
511
		if (is_string($sort)) {
512
			$sort = [$sort => 'ASC'];
513
		} else {
514
			$sort = (array) $sort;
515
		}
516
517
		$this->default_sort = $sort;
518
519
		return $this;
520
	}
521
522
523
	/**
524
	 * User may set default sorting, apply it
525
	 * @return void
526
	 */
527
	public function findDefaultSort()
528
	{
529
		if (!empty($this->sort)) {
530
			return;
531
		}
532
533
		if (!empty($this->default_sort)) {
534
			$this->sort = $this->default_sort;
535
		}
536
537
		$this->saveSessionData('_grid_sort', $this->sort);
538
	}
539
540
541
	/**
542
	 * Set grido to be sortable
543
	 * @param bool $sortable
544
	 * @return static
545
	 */
546
	public function setSortable($sortable = TRUE)
547
	{
548
		if ($this->getItemsDetail()) {
549
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
550
		}
551
552
		$this->sortable = (bool) $sortable;
553
554
		return $this;
555
	}
556
557
558
	/**
559
	 * Set sortable handle
560
	 * @param string $handler
561
	 * @return static
562
	 */
563
	public function setSortableHandler($handler = 'sort!')
564
	{
565
		$this->sortable_handler = (string) $handler;
566
567
		return $this;
568
	}
569
570
571
	/**
572
	 * Tell whether DataGrid is sortable
573
	 * @return bool
574
	 */
575
	public function isSortable()
576
	{
577
		return $this->sortable;
578
	}
579
580
	/**
581
	 * Return sortable handle name
582
	 * @return string
583
	 */
584
	public function getSortableHandler()
585
	{
586
		return $this->sortable_handler;
587
	}
588
589
590
	/********************************************************************************
591
	 *                                  TREE VIEW                                   *
592
	 ********************************************************************************/
593
594
595
	/**
596
	 * Is tree view set?
597
	 * @return boolean
598
	 */
599
	public function isTreeView()
600
	{
601
		return (bool) $this->tree_view_children_callback;
602
	}
603
604
605
	/**
606
	 * Setting tree view
607
	 * @param callable $get_children_callback
608
	 * @param string|callable $tree_view_has_children_column
609
	 * @return static
610
	 */
611
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
612
	{
613
		if (!is_callable($get_children_callback)) {
614
			throw new DataGridException(
615
				'Parameters to method DataGrid::setTreeView must be of type callable'
616
			);
617
		}
618
619
		if (is_callable($tree_view_has_children_column)) {
620
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
621
			$tree_view_has_children_column = NULL;
622
		}
623
624
		$this->tree_view_children_callback = $get_children_callback;
625
		$this->tree_view_has_children_column = $tree_view_has_children_column;
626
627
		/**
628
		 * TUrn off pagination
629
		 */
630
		$this->setPagination(FALSE);
631
632
		/**
633
		 * Set tree view template file
634
		 */
635
		if (!$this->template_file) {
636
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
637
		}
638
639
		return $this;
640
	}
641
642
643
	/**
644
	 * Is tree view children callback set?
645
	 * @return boolean
646
	 */
647
	public function hasTreeViewChildrenCallback()
648
	{
649
		return is_callable($this->tree_view_has_children_callback);
650
	}
651
652
653
	/**
654
	 * @param  mixed $item
655
	 * @return boolean
656
	 */
657
	public function treeViewChildrenCallback($item)
658
	{
659
		return call_user_func($this->tree_view_has_children_callback, $item);
660
	}
661
662
663
	/********************************************************************************
664
	 *                                    COLUMNS                                   *
665
	 ********************************************************************************/
666
667
668
	/**
669
	 * Add text column with no other formating
670
	 * @param  string      $key
671
	 * @param  string      $name
672
	 * @param  string|null $column
673
	 * @return Column\ColumnText
674
	 */
675
	public function addColumnText($key, $name, $column = NULL)
676
	{
677
		$this->addColumnCheck($key);
678
		$column = $column ?: $key;
679
680
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
681
	}
682
683
684
	/**
685
	 * Add column with link
686
	 * @param  string      $key
687
	 * @param  string      $name
688
	 * @param  string|null $column
689
	 * @return Column\ColumnLink
690
	 */
691
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
692
	{
693
		$this->addColumnCheck($key);
694
		$column = $column ?: $key;
695
		$href = $href ?: $key;
696
697
		if (NULL === $params) {
698
			$params = [$this->primary_key];
699
		}
700
701
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
702
	}
703
704
705
	/**
706
	 * Add column with possible number formating
707
	 * @param  string      $key
708
	 * @param  string      $name
709
	 * @param  string|null $column
710
	 * @return Column\ColumnNumber
711
	 */
712
	public function addColumnNumber($key, $name, $column = NULL)
713
	{
714
		$this->addColumnCheck($key);
715
		$column = $column ?: $key;
716
717
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
718
	}
719
720
721
	/**
722
	 * Add column with date formating
723
	 * @param  string      $key
724
	 * @param  string      $name
725
	 * @param  string|null $column
726
	 * @return Column\ColumnDateTime
727
	 */
728
	public function addColumnDateTime($key, $name, $column = NULL)
729
	{
730
		$this->addColumnCheck($key);
731
		$column = $column ?: $key;
732
733
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
734
	}
735
736
737
	/**
738
	 * Add column status
739
	 * @param  string      $key
740
	 * @param  string      $name
741
	 * @param  string|null $column
742
	 * @return Column\ColumnStatus
743
	 */
744
	public function addColumnStatus($key, $name, $column = NULL)
745
	{
746
		$this->addColumnCheck($key);
747
		$column = $column ?: $key;
748
749
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
750
	}
751
752
753
	/**
754
	 * @param string $key
755
	 * @param Column\Column $column
756
	 * @return Column\Column
757
	 */
758
	protected function addColumn($key, Column\Column $column)
759
	{
760
		$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...
761
762
		$this->columns_visibility[$key] = [
763
			'visible' => TRUE,
764
			'name' => $column->getName()
765
		];
766
767
		return $this->columns[$key] = $column;
768
	}
769
770
771
	/**
772
	 * Return existing column
773
	 * @param  string $key
774
	 * @return Column\Column
775
	 * @throws DataGridException
776
	 */
777
	public function getColumn($key)
778
	{
779
		if (!isset($this->columns[$key])) {
780
			throw new DataGridException("There is no column at key [$key] defined.");
781
		}
782
783
		return $this->columns[$key];
784
	}
785
786
787
	/**
788
	 * Remove column
789
	 * @param string $key
790
	 * @return void
791
	 */
792
	public function removeColumn($key)
793
	{
794
		unset($this->columns[$key]);
795
	}
796
797
798
	/**
799
	 * Check whether given key already exists in $this->columns
800
	 * @param  string $key
801
	 * @throws DataGridException
802
	 */
803
	protected function addColumnCheck($key)
804
	{
805
		if (isset($this->columns[$key])) {
806
			throw new DataGridException("There is already column at key [$key] defined.");
807
		}
808
	}
809
810
811
	/********************************************************************************
812
	 *                                    ACTIONS                                   *
813
	 ********************************************************************************/
814
815
816
	/**
817
	 * Create action
818
	 * @param string     $key
819
	 * @param string     $name
820
	 * @param string     $href
821
	 * @param array|null $params
822
	 * @return Column\Action
823
	 */
824
	public function addAction($key, $name, $href = NULL, array $params = NULL)
825
	{
826
		$this->addActionCheck($key);
827
		$href = $href ?: $key;
828
829
		if (NULL === $params) {
830
			$params = [$this->primary_key];
831
		}
832
833
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
834
	}
835
836
837
	/**
838
	 * Create action callback
839
	 * @param string     $key
840
	 * @param string     $name
841
	 * @return Column\Action
842
	 */
843
	public function addActionCallback($key, $name, $callback = NULL)
844
	{
845
		$this->addActionCheck($key);
846
		$params = ['__id' => $this->primary_key];
847
848
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
849
850
		if ($callback) {
851
			if (!is_callable($callback)) {
852
				throw new DataGridException('ActionCallback callback has to be callable.');
853
			}
854
855
			$action->onClick[] = $callback;
856
		}
857
858
		return $action;
859
	}
860
861
862
	/**
863
	 * Get existing action
864
	 * @param  string       $key
865
	 * @return Column\Action
866
	 * @throws DataGridException
867
	 */
868
	public function getAction($key)
869
	{
870
		if (!isset($this->actions[$key])) {
871
			throw new DataGridException("There is no action at key [$key] defined.");
872
		}
873
874
		return $this->actions[$key];
875
	}
876
877
878
	/**
879
	 * Remove action
880
	 * @param string $key
881
	 * @return void
882
	 */
883
	public function removeAction($key)
884
	{
885
		unset($this->actions[$key]);
886
	}
887
888
889
	/**
890
	 * Check whether given key already exists in $this->filters
891
	 * @param  string $key
892
	 * @throws DataGridException
893
	 */
894
	protected function addActionCheck($key)
895
	{
896
		if (isset($this->actions[$key])) {
897
			throw new DataGridException("There is already action at key [$key] defined.");
898
		}
899
	}
900
901
902
	/********************************************************************************
903
	 *                                    FILTERS                                   *
904
	 ********************************************************************************/
905
906
907
	/**
908
	 * Add filter fot text search
909
	 * @param string       $key
910
	 * @param string       $name
911
	 * @param array|string $columns
912
	 * @return Filter\FilterText
913
	 * @throws DataGridException
914
	 */
915
	public function addFilterText($key, $name, $columns = NULL)
916
	{
917
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
918
919
		if (!is_array($columns)) {
920
			throw new DataGridException("Filter Text can except only array or string.");
921
		}
922
923
		$this->addFilterCheck($key);
924
925
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
926
	}
927
928
929
	/**
930
	 * Add select box filter
931
	 * @param string $key
932
	 * @param string $name
933
	 * @param array  $options
934
	 * @param string $column
935
	 * @return Filter\FilterSelect
936
	 * @throws DataGridException
937
	 */
938 View Code Duplication
	public function addFilterSelect($key, $name, array $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...
939
	{
940
		$column = $column ?: $key;
941
942
		if (!is_string($column)) {
943
			throw new DataGridException("Filter Select can only filter through one column.");
944
		}
945
946
		$this->addFilterCheck($key);
947
948
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
949
	}
950
951
952
	/**
953
	 * Add datepicker filter
954
	 * @param string $key
955
	 * @param string $name
956
	 * @param string $column
957
	 * @return Filter\FilterDate
958
	 * @throws DataGridException
959
	 */
960
	public function addFilterDate($key, $name, $column = NULL)
961
	{
962
		$column = $column ?: $key;
963
964
		if (!is_string($column)) {
965
			throw new DataGridException("FilterDate can only filter through one column.");
966
		}
967
968
		$this->addFilterCheck($key);
969
970
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
971
	}
972
973
974
	/**
975
	 * Add range filter (from - to)
976
	 * @param string $key
977
	 * @param string $name
978
	 * @param string $column
979
	 * @return Filter\FilterRange
980
	 * @throws DataGridException
981
	 */
982 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...
983
	{
984
		$column = $column ?: $key;
985
986
		if (!is_string($column)) {
987
			throw new DataGridException("FilterRange can only filter through one column.");
988
		}
989
990
		$this->addFilterCheck($key);
991
992
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
993
	}
994
995
996
	/**
997
	 * Add datepicker filter (from - to)
998
	 * @param string $key
999
	 * @param string $name
1000
	 * @param string $column
1001
	 * @return Filter\FilterDateRange
1002
	 * @throws DataGridException
1003
	 */
1004 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...
1005
	{
1006
		$column = $column ?: $key;
1007
1008
		if (!is_string($column)) {
1009
			throw new DataGridException("FilterDateRange can only filter through one column.");
1010
		}
1011
1012
		$this->addFilterCheck($key);
1013
1014
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
1015
	}
1016
1017
1018
	/**
1019
	 * Check whether given key already exists in $this->filters
1020
	 * @param  string $key
1021
	 * @throws DataGridException
1022
	 */
1023
	protected function addFilterCheck($key)
1024
	{
1025
		if (isset($this->filters[$key])) {
1026
			throw new DataGridException("There is already action at key [$key] defined.");
1027
		}
1028
	}
1029
1030
1031
	/**
1032
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1033
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1034
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1035
	 */
1036
	public function assableFilters()
1037
	{
1038
		foreach ($this->filter as $key => $value) {
1039
			if (!isset($this->filters[$key])) {
1040
				$this->deleteSesssionData($key);
1041
1042
				continue;
1043
			}
1044
1045
			if (is_array($value) || $value instanceof \Traversable) {
1046
				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...
1047
					$this->filters[$key]->setValue($value);
1048
				}
1049
			} else {
1050
				if ($value !== '' && $value !== NULL) {
1051
					$this->filters[$key]->setValue($value);
1052
				}
1053
			}
1054
		}
1055
1056
		foreach ($this->columns as $column) {
1057
			if (isset($this->sort[$column->getSortingColumn()])) {
1058
				$column->setSort($this->sort);
1059
			}
1060
		}
1061
1062
		return $this->filters;
1063
	}
1064
1065
1066
	/**
1067
	 * Remove filter
1068
	 * @param string $key
1069
	 * @return void
1070
	 */
1071
	public function removeFilter($key)
1072
	{
1073
		unset($this->filters[$key]);
1074
	}
1075
1076
1077
	/********************************************************************************
1078
	 *                                  FILTERING                                   *
1079
	 ********************************************************************************/
1080
1081
1082
	/**
1083
	 * Is filter active?
1084
	 * @return boolean
1085
	 */
1086
	public function isFilterActive()
1087
	{
1088
		$is_filter = ArraysHelper::testTruthy($this->filter);
1089
1090
		return ($is_filter) || $this->force_filter_active;
1091
	}
1092
1093
1094
	/**
1095
	 * Tell that filter is active from whatever reasons
1096
	 * return static
1097
	 */
1098
	public function setFilterActive()
1099
	{
1100
		$this->force_filter_active = TRUE;
1101
1102
		return $this;
1103
	}
1104
1105
1106
	/**
1107
	 * If we want to sent some initial filter
1108
	 * @param array $filter
1109
	 * @return static
1110
	 */
1111
	public function setFilter(array $filter)
1112
	{
1113
		$this->filter = $filter;
1114
1115
		return $this;
1116
	}
1117
1118
1119
	/**
1120
	 * FilterAndGroupAction form factory
1121
	 * @return Form
1122
	 */
1123
	public function createComponentFilter()
1124
	{
1125
		$form = new Form($this, 'filter');
1126
1127
		$form->setMethod('get');
1128
1129
		$form->setTranslator($this->getTranslator());
1130
1131
		/**
1132
		 * InlineEdit part
1133
		 */
1134
		$inline_edit_container = $form->addContainer('inline_edit');
1135
1136
		if ($this->inlineEdit instanceof InlineEdit) {
1137
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1138
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1139
				->setValidationScope(FALSE);
1140
1141
			$this->inlineEdit->onControlAdd($inline_edit_container);
1142
		}
1143
1144
		/**
1145
		 * Filter part
1146
		 */
1147
		$filter_container = $form->addContainer('filter');
1148
1149
		foreach ($this->filters as $filter) {
1150
			$filter->addToFormContainer($filter_container);
1151
		}
1152
1153
		/**
1154
		 * Group action part
1155
		 */
1156
		$group_action_container = $form->addContainer('group_action');
1157
1158
		if ($this->hasGroupActions()) {
1159
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1160
		}
1161
1162
		$form->setDefaults(['filter' => $this->filter]);
1163
1164
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1165
1166
		return $form;
1167
	}
1168
1169
1170
	/**
1171
	 * Set $this->filter values after filter form submitted
1172
	 * @param  Form $form
1173
	 * @return void
1174
	 */
1175
	public function filterSucceeded(Form $form)
1176
	{
1177
		$values = $form->getValues();
1178
1179
		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...
1180
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1181
				return;
1182
			}
1183
		}
1184
1185
		$inline_edit = $form['inline_edit'];
1186
1187
		if (isset($inline_edit) && isset($inline_edit['submit']) && isset($inline_edit['cancel'])) {
1188
			if ($inline_edit['submit']->isSubmittedBy() || $inline_edit['cancel']->isSubmittedBy()) {
1189
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1190
				$primary_where_column = $form->getHttpData(
1191
					Form::DATA_LINE,
1192
					'inline_edit[_primary_where_column]'
1193
				);
1194
1195
				if ($inline_edit['submit']->isSubmittedBy()) {
1196
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1197
1198
					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...
1199
						$this->getPresenter()->payload->_datagrid_inline_edited = $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...
1200
					}
1201
				}
1202
1203
				$this->redrawItem($id, $primary_where_column);
1204
1205
				return;
1206
			}
1207
		}
1208
1209
		$values = $values['filter'];
1210
1211
		foreach ($values as $key => $value) {
1212
			/**
1213
			 * Session stuff
1214
			 */
1215
			$this->saveSessionData($key, $value);
1216
1217
			/**
1218
			 * Other stuff
1219
			 */
1220
			$this->filter[$key] = $value;
1221
		}
1222
1223
		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...
1224
			$this->getPresenter()->payload->_datagrid_sort = [];
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...
1225
1226
			foreach ($this->columns as $key => $column) {
1227
				if ($column->isSortable()) {
1228
					$this->getPresenter()->payload->_datagrid_sort[$key] = $this->link('sort!', [
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...
1229
						'sort' => $column->getSortNext()
1230
					]);
1231
				}
1232
			}
1233
		}
1234
1235
		$this->reload();
1236
	}
1237
1238
1239
	/**
1240
	 * Should be datagrid filters rendered separately?
1241
	 * @param boolean $out
1242
	 * @return static
1243
	 */
1244
	public function setOuterFilterRendering($out = TRUE)
1245
	{
1246
		$this->outer_filter_rendering = (bool) $out;
1247
1248
		return $this;
1249
	}
1250
1251
1252
	/**
1253
	 * Are datagrid filters rendered separately?
1254
	 * @return boolean
1255
	 */
1256
	public function hasOuterFilterRendering()
1257
	{
1258
		return $this->outer_filter_rendering;
1259
	}
1260
1261
1262
	/**
1263
	 * Try to restore session stuff
1264
	 * @return void
1265
	 */
1266
	public function findSessionFilters()
1267
	{
1268
		if ($this->filter || ($this->page != 1) || !empty($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...
1269
			return;
1270
		}
1271
1272
		if (!$this->remember_state) {
1273
			return;
1274
		}
1275
1276
		if ($page = $this->getSessionData('_grid_page')) {
1277
			$this->page = $page;
1278
		}
1279
1280
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1281
			$this->per_page = $per_page;
1282
		}
1283
1284
		if ($sort = $this->getSessionData('_grid_sort')) {
1285
			$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...
1286
		}
1287
1288
		foreach ($this->getSessionData() as $key => $value) {
1289
			$other_session_keys = [
1290
				'_grid_per_page',
1291
				'_grid_sort',
1292
				'_grid_page',
1293
				'_grid_hidden_columns',
1294
				'_grid_hidden_columns_manipulated'
1295
			];
1296
1297
			if (!in_array($key, $other_session_keys)) {
1298
				$this->filter[$key] = $value;
1299
			}
1300
		}
1301
	}
1302
1303
1304
	/********************************************************************************
1305
	 *                                    EXPORTS                                   *
1306
	 ********************************************************************************/
1307
1308
1309
	/**
1310
	 * Add export of type callback
1311
	 * @param string $text
1312
	 * @param callable $callback
1313
	 * @param boolean $filtered
1314
	 * @return Export\Export
1315
	 */
1316
	public function addExportCallback($text, $callback, $filtered = FALSE)
1317
	{
1318
		if (!is_callable($callback)) {
1319
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1320
		}
1321
1322
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
1323
	}
1324
1325
1326
	/**
1327
	 * Add already implemented csv export
1328
	 * @param string      $text
1329
	 * @param string      $csv_file_name
1330
	 * @param string|null $output_encoding
1331
	 * @param string|null $delimiter
1332
	 * @return Export\Export
1333
	 */
1334
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1335
	{
1336
		return $this->addToExports(new Export\ExportCsv(
1337
			$text,
1338
			$csv_file_name,
1339
			FALSE,
1340
			$output_encoding,
1341
			$delimiter
1342
		));
1343
	}
1344
1345
1346
	/**
1347
	 * Add already implemented csv export, but for filtered data
1348
	 * @param string      $text
1349
	 * @param string      $csv_file_name
1350
	 * @param string|null $output_encoding
1351
	 * @param string|null $delimiter
1352
	 * @return Export\Export
1353
	 */
1354
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1355
	{
1356
		return $this->addToExports(new Export\ExportCsv(
1357
			$text,
1358
			$csv_file_name,
1359
			TRUE,
1360
			$output_encoding,
1361
			$delimiter
1362
		));
1363
	}
1364
1365
1366
	/**
1367
	 * Add export to array
1368
	 * @param Export\Export $export
1369
	 * @return Export\Export
1370
	 */
1371
	protected function addToExports(Export\Export $export)
1372
	{
1373
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1374
1375
		$export->setLink($this->link('export!', ['id' => $id]));
1376
1377
		return $this->exports[$id] = $export;
1378
	}
1379
1380
1381
	public function resetExportsLinks()
1382
	{
1383
		foreach ($this->exports as $id => $export) {
1384
			$export->setLink($this->link('export!', ['id' => $id]));
1385
		}
1386
	}
1387
1388
1389
	/********************************************************************************
1390
	 *                                 GROUP ACTIONS                                *
1391
	 ********************************************************************************/
1392
1393
1394
	/**
1395
	 * Alias for add group select action
1396
	 * @param string $title
1397
	 * @param array  $options
1398
	 * @return GroupAction\GroupAction
1399
	 */
1400
	public function addGroupAction($title, $options = [])
1401
	{
1402
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1403
	}
1404
1405
	/**
1406
	 * Add group action (select box)
1407
	 * @param string $title
1408
	 * @param array  $options
1409
	 * @return GroupAction\GroupAction
1410
	 */
1411
	public function addGroupSelectAction($title, $options = [])
1412
	{
1413
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1414
	}
1415
1416
	/**
1417
	 * Add group action (text input)
1418
	 * @param string $title
1419
	 * @return GroupAction\GroupAction
1420
	 */
1421
	public function addGroupTextAction($title)
1422
	{
1423
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1424
	}
1425
1426
	/**
1427
	 * Get collection of all group actions
1428
	 * @return GroupAction\GroupActionCollection
1429
	 */
1430
	public function getGroupActionCollection()
1431
	{
1432
		if (!$this->group_action_collection) {
1433
			$this->group_action_collection = new GroupAction\GroupActionCollection();
1434
		}
1435
1436
		return $this->group_action_collection;
1437
	}
1438
1439
1440
	/**
1441
	 * Has datagrid some group actions?
1442
	 * @return boolean
1443
	 */
1444
	public function hasGroupActions()
1445
	{
1446
		return (bool) $this->group_action_collection;
1447
	}
1448
1449
1450
	/********************************************************************************
1451
	 *                                   HANDLERS                                   *
1452
	 ********************************************************************************/
1453
1454
1455
	/**
1456
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1457
	 * @param  int  $page
1458
	 * @return void
1459
	 */
1460
	public function handlePage($page)
1461
	{
1462
		/**
1463
		 * Session stuff
1464
		 */
1465
		$this->page = $page;
1466
		$this->saveSessionData('_grid_page', $page);
1467
1468
		$this->reload(['table']);
1469
	}
1470
1471
1472
	/**
1473
	 * Handler for sorting
1474
	 * @param array $sort
1475
	 * @return void
1476
	 */
1477
	public function handleSort(array $sort)
1478
	{
1479
		$new_sort = [];
1480
1481
		/**
1482
		 * Find apropirate column
1483
		 */
1484
		foreach ($sort as $key => $value) {
1485
			if (empty($this->columns[$key])) {
1486
				throw new DataGridException("Column <$key> not found");
1487
			}
1488
1489
			$column = $this->columns[$key];
1490
			$new_sort = [$column->getSortingColumn() => $value];
1491
1492
			/**
1493
			 * Pagination may be reseted after sorting
1494
			 */
1495
			if ($column->sortableResetPagination()) {
1496
				$this->page = 1;
1497
				$this->saveSessionData('_grid_page', 1);
1498
			}
1499
1500
			/**
1501
			 * Custom sorting callback may be applied
1502
			 */
1503
			if ($column->getSortableCallback()) {
1504
				$this->sort_callback = $column->getSortableCallback();
1505
			}
1506
		}
1507
1508
		/**
1509
		 * Session stuff
1510
		 */
1511
		$this->sort = $new_sort;
1512
		$this->saveSessionData('_grid_sort', $this->sort);
1513
1514
		$this->reload(['table']);
1515
	}
1516
1517
1518
	/**
1519
	 * handler for reseting the filter
1520
	 * @return void
1521
	 */
1522
	public function handleResetFilter()
1523
	{
1524
		/**
1525
		 * Session stuff
1526
		 */
1527
		$this->deleteSesssionData('_grid_page');
1528
1529
		foreach ($this->getSessionData() as $key => $value) {
1530
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page'])) {
1531
				$this->deleteSesssionData($key);
1532
			}
1533
		}
1534
1535
		$this->filter = [];
1536
1537
		$this->reload(['grid']);
1538
	}
1539
1540
1541
	/**
1542
	 * Handler for export
1543
	 * @param  int $id Key for particular export class in array $this->exports
1544
	 * @return void
1545
	 */
1546
	public function handleExport($id)
1547
	{
1548
		if (!isset($this->exports[$id])) {
1549
			throw new Nette\Application\ForbiddenRequestException;
1550
		}
1551
1552
		if (!empty($this->columns_export_order)) {
1553
			$this->setColumnsOrder($this->columns_export_order);
1554
		}
1555
1556
		$export = $this->exports[$id];
1557
1558
		if ($export->isFiltered()) {
1559
			$sort      = $this->sort;
0 ignored issues
show
Unused Code introduced by
$sort is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1560
			$filter    = $this->assableFilters();
1561
		} else {
1562
			$sort      = [$this->primary_key => 'ASC'];
0 ignored issues
show
Unused Code introduced by
$sort is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1563
			$filter    = [];
1564
		}
1565
1566
		if (NULL === $this->dataModel) {
1567
			throw new DataGridException('You have to set a data source first.');
1568
		}
1569
1570
		$rows = [];
1571
1572
		$items = Nette\Utils\Callback::invokeArgs(
1573
			[$this->dataModel, 'filterData'], [
1574
				NULL,
1575
				new Sorting($this->sort, $this->sort_callback),
1576
				$filter
1577
			]
1578
		);
1579
1580
		foreach ($items as $item) {
1581
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1582
		}
1583
1584
		if ($export instanceof Export\ExportCsv) {
1585
			$export->invoke($rows, $this);
1586
		} else {
1587
			$export->invoke($items, $this);
1588
		}
1589
1590
		if ($export->isAjax()) {
1591
			$this->reload();
1592
		}
1593
	}
1594
1595
1596
	/**
1597
	 * Handler for getting children of parent item (e.g. category)
1598
	 * @param  int $parent
1599
	 * @return void
1600
	 */
1601
	public function handleGetChildren($parent)
1602
	{
1603
		$this->setDataSource(
1604
			call_user_func($this->tree_view_children_callback, $parent)
1605
		);
1606
1607
		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...
1608
			$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...
1609
			$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...
1610
1611
			$this->redrawControl('items');
1612
1613
			$this->onRedraw();
1614
		} else {
1615
			$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...
1616
		}
1617
	}
1618
1619
1620
	/**
1621
	 * Handler for getting item detail
1622
	 * @param  mixed $id
1623
	 * @return void
1624
	 */
1625
	public function handleGetItemDetail($id)
1626
	{
1627
		$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...
1628
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1629
1630
		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...
1631
			$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...
1632
			$this->redrawControl('items');
1633
1634
			$this->onRedraw();
1635
		} else {
1636
			$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...
1637
		}
1638
	}
1639
1640
1641
	/**
1642
	 * Handler for inline editing
1643
	 * @param  mixed $id
1644
	 * @param  mixed $key
1645
	 * @return void
1646
	 */
1647
	public function handleEdit($id, $key)
1648
	{
1649
		$column = $this->getColumn($key);
1650
		$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...
1651
1652
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1653
	}
1654
1655
1656
	/**
1657
	 * Redraw $this
1658
	 * @return void
1659
	 */
1660
	public function reload($snippets = [])
1661
	{
1662
		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...
1663
			$this->redrawControl('tbody');
1664
			$this->redrawControl('pagination');
1665
1666
			/**
1667
			 * manualy reset exports links...
1668
			 */
1669
			$this->resetExportsLinks();
1670
			$this->redrawControl('exports');
1671
1672
			foreach ($snippets as $snippet) {
1673
				$this->redrawControl($snippet);
1674
			}
1675
1676
			$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...
1677
1678
			$this->onRedraw();
1679
		} else {
1680
			$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...
1681
		}
1682
	}
1683
1684
1685
	/**
1686
	 * Handler for column status
1687
	 * @param  string $id
1688
	 * @param  string $key
1689
	 * @param  string $value
1690
	 * @return void
1691
	 */
1692
	public function handleChangeStatus($id, $key, $value)
1693
	{
1694
		if (empty($this->columns[$key])) {
1695
			throw new DataGridException("ColumnStatus[$key] does not exist");
1696
		}
1697
1698
		$this->columns[$key]->onChange($id, $value);
1699
	}
1700
1701
1702
	/**
1703
	 * Redraw just one row via ajax
1704
	 * @param  int   $id
1705
	 * @param  mixed $primary_where_column
1706
	 * @return void
1707
	 */
1708
	public function redrawItem($id, $primary_where_column = NULL)
1709
	{
1710
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1711
1712
		$this->redrawControl('items');
1713
		$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...
1714
1715
		$this->onRedraw();
1716
	}
1717
1718
1719
	/**
1720
	 * Tell datagrid to display all columns
1721
	 * @return void
1722
	 */
1723
	public function handleShowAllColumns()
1724
	{
1725
		$this->deleteSesssionData('_grid_hidden_columns');
1726
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1727
1728
		$this->redrawControl();
1729
1730
		$this->onRedraw();
1731
	}
1732
1733
1734
	/**
1735
	 * Reveal particular column
1736
	 * @param  string $column
1737
	 * @return void
1738
	 */
1739 View Code Duplication
	public function handleShowColumn($column)
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...
1740
	{
1741
		$columns = $this->getSessionData('_grid_hidden_columns');
1742
1743
		if (!empty($columns)) {
1744
			$pos = array_search($column, $columns);
1745
1746
			if ($pos !== FALSE) {
1747
				unset($columns[$pos]);
1748
			}
1749
		}
1750
1751
		$this->saveSessionData('_grid_hidden_columns', $columns);
1752
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1753
1754
		$this->redrawControl();
1755
1756
		$this->onRedraw();
1757
	}
1758
1759
1760
	/**
1761
	 * Notice datagrid to not display particular columns
1762
	 * @param  string $column
1763
	 * @return void
1764
	 */
1765 View Code Duplication
	public function handleHideColumn($column)
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...
1766
	{
1767
		/**
1768
		 * Store info about hiding a column to session
1769
		 */
1770
		$columns = $this->getSessionData('_grid_hidden_columns');
1771
1772
		if (empty($columns)) {
1773
			$columns = [$column];
1774
		} else if (!in_array($column, $columns)) {
1775
			array_push($columns, $column);
1776
		}
1777
1778
		$this->saveSessionData('_grid_hidden_columns', $columns);
1779
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1780
1781
		$this->redrawControl();
1782
1783
		$this->onRedraw();
1784
	}
1785
1786
1787
	public function handleActionCallback($__key, $__id)
1788
	{
1789
		$action = $this->getAction($__key);
1790
1791
		if (!($action instanceof Column\ActionCallback)) {
1792
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
1793
		}
1794
1795
		$action->onClick($__id);
1796
	}
1797
1798
1799
	/********************************************************************************
1800
	 *                                  PAGINATION                                  *
1801
	 ********************************************************************************/
1802
1803
1804
	/**
1805
	 * Set options of select "items_per_page"
1806
	 * @param array $items_per_page_list
1807
	 * @return static
1808
	 */
1809
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
1810
	{
1811
		$this->items_per_page_list = $items_per_page_list;
1812
1813
		if ($include_all) {
1814
			$this->items_per_page_list[] = 'all';
1815
		}
1816
1817
		return $this;
1818
	}
1819
1820
1821
	/**
1822
	 * Paginator factory
1823
	 * @return Components\DataGridPaginator\DataGridPaginator
1824
	 */
1825
	public function createComponentPaginator()
1826
	{
1827
		/**
1828
		 * Init paginator
1829
		 */
1830
		$component = new Components\DataGridPaginator\DataGridPaginator(
1831
			$this->getTranslator()
1832
		);
1833
		$paginator = $component->getPaginator();
1834
1835
		$paginator->setPage($this->page);
1836
		$paginator->setItemsPerPage($this->getPerPage());
1837
1838
		return $component;
1839
	}
1840
1841
1842
	/**
1843
	 * PerPage form factory
1844
	 * @return Form
1845
	 */
1846
	public function createComponentPerPage()
1847
	{
1848
		$form = new Form;
1849
1850
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1851
			->setValue($this->getPerPage());
1852
1853
		$form->addSubmit('submit', '');
1854
1855
		$saveSessionData = [$this, 'saveSessionData'];
1856
1857
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
1858
			/**
1859
			 * Session stuff
1860
			 */
1861
			$saveSessionData('_grid_per_page', $values->per_page);
1862
1863
			/**
1864
			 * Other stuff
1865
			 */
1866
			$this->per_page = $values->per_page;
1867
			$this->reload();
1868
		};
1869
1870
		return $form;
1871
	}
1872
1873
1874
	/**
1875
	 * Get parameter per_page
1876
	 * @return int
1877
	 */
1878
	public function getPerPage()
1879
	{
1880
		$items_per_page_list = $this->getItemsPerPageList();
1881
1882
		$per_page = $this->per_page ?: reset($items_per_page_list);
1883
1884
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
1885
			$per_page = reset($items_per_page_list);
1886
		}
1887
1888
		return $per_page;
1889
	}
1890
1891
1892
	/**
1893
	 * Get associative array of items_per_page_list
1894
	 * @return array
1895
	 */
1896
	public function getItemsPerPageList()
1897
	{
1898
		if (empty($this->items_per_page_list)) {
1899
			$this->setItemsPerPageList([10, 20, 50], TRUE);
1900
		}
1901
1902
		$list = array_flip($this->items_per_page_list);
1903
1904
		foreach ($list as $key => $value) {
1905
			$list[$key] = $key;
1906
		}
1907
1908
		if (array_key_exists('all', $list)) {
1909
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
1910
		}
1911
1912
		return $list;
1913
	}
1914
1915
1916
	/**
1917
	 * Order Grid to "be paginated"
1918
	 * @param bool $do
1919
	 * @return static
1920
	 */
1921
	public function setPagination($do)
1922
	{
1923
		$this->do_paginate = (bool) $do;
1924
1925
		return $this;
1926
	}
1927
1928
1929
	/**
1930
	 * Tell whether Grid is paginated
1931
	 * @return bool
1932
	 */
1933
	public function isPaginated()
1934
	{
1935
		return $this->do_paginate;
1936
	}
1937
1938
1939
	/**
1940
	 * Return current paginator class
1941
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
1942
	 */
1943
	public function getPaginator()
1944
	{
1945
		if ($this->isPaginated() && $this->per_page !== 'all') {
1946
			return $this['paginator'];
1947
		}
1948
1949
		return NULL;
1950
	}
1951
1952
1953
	/********************************************************************************
1954
	 *                                     I18N                                     *
1955
	 ********************************************************************************/
1956
1957
1958
	/**
1959
	 * Set datagrid translator
1960
	 * @param Nette\Localization\ITranslator $translator
1961
	 * @return static
1962
	 */
1963
	public function setTranslator(Nette\Localization\ITranslator $translator)
1964
	{
1965
		$this->translator = $translator;
1966
1967
		return $this;
1968
	}
1969
1970
1971
	/**
1972
	 * Get translator for datagrid
1973
	 * @return Nette\Localization\ITranslator
1974
	 */
1975
	public function getTranslator()
1976
	{
1977
		if (!$this->translator) {
1978
			$this->translator = new Localization\SimpleTranslator;
1979
		}
1980
1981
		return $this->translator;
1982
	}
1983
1984
1985
	/********************************************************************************
1986
	 *                                 COLUMNS ORDER                                *
1987
	 ********************************************************************************/
1988
1989
1990
	/**
1991
	 * Set order of datagrid columns
1992
	 * @param array $order
1993
	 * @return static
1994
	 */
1995
	public function setColumnsOrder($order)
1996
	{
1997
		$new_order = [];
1998
1999
		foreach ($order as $key) {
2000
			if (isset($this->columns[$key])) {
2001
				$new_order[$key] = $this->columns[$key];
2002
			}
2003
		}
2004
2005
		if (sizeof($new_order) === sizeof($this->columns)) {
2006
			$this->columns = $new_order;
2007
		} else {
2008
			throw new DataGridException('When changing columns order, you have to specify all columns');
2009
		}
2010
2011
		return $this;
2012
	}
2013
2014
2015
	/**
2016
	 * Columns order may be different for export and normal grid
2017
	 * @param array $order
2018
	 */
2019
	public function setColumnsExportOrder($order)
2020
	{
2021
		$this->columns_export_order = (array) $order;
2022
	}
2023
2024
2025
	/********************************************************************************
2026
	 *                                SESSION & URL                                 *
2027
	 ********************************************************************************/
2028
2029
2030
	/**
2031
	 * Find some unique session key name
2032
	 * @return string
2033
	 */
2034
	public function getSessionSectionName()
2035
	{
2036
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2037
	}
2038
2039
2040
	/**
2041
	 * Should datagrid remember its filters/pagination/etc using session?
2042
	 * @param bool $remember
2043
	 * @return static
2044
	 */
2045
	public function setRememberState($remember = TRUE)
2046
	{
2047
		$this->remember_state = (bool) $remember;
2048
2049
		return $this;
2050
	}
2051
2052
2053
	/**
2054
	 * Should datagrid refresh url using history API?
2055
	 * @param bool $refresh
2056
	 * @return static
2057
	 */
2058
	public function setRefreshUrl($refresh = TRUE)
2059
	{
2060
		$this->refresh_url = (bool) $refresh;
2061
2062
2063
		return $this;
2064
	}
2065
2066
2067
	/**
2068
	 * Get session data if functionality is enabled
2069
	 * @param  string $key
2070
	 * @return mixed
2071
	 */
2072
	public function getSessionData($key = NULL, $default_value = NULL)
2073
	{
2074
		if (!$this->remember_state) {
2075
			return $key ? $default_value : [];
2076
		}
2077
2078
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2079
	}
2080
2081
2082
	/**
2083
	 * Save session data - just if it is enabled
2084
	 * @param  string $key
2085
	 * @param  mixed  $value
2086
	 * @return void
2087
	 */
2088
	public function saveSessionData($key, $value)
2089
	{
2090
		if ($this->remember_state) {
2091
			$this->grid_session->{$key} = $value;
2092
		}
2093
	}
2094
2095
2096
	/**
2097
	 * Delete session data
2098
	 * @return void
2099
	 */
2100
	public function deleteSesssionData($key)
2101
	{
2102
		unset($this->grid_session->{$key});
2103
	}
2104
2105
2106
	/********************************************************************************
2107
	 *                                  ITEM DETAIL                                 *
2108
	 ********************************************************************************/
2109
2110
2111
	/**
2112
	 * Get items detail parameters
2113
	 * @return array
2114
	 */
2115
	public function getItemsDetail()
2116
	{
2117
		return $this->items_detail;
2118
	}
2119
2120
2121
	/**
2122
	 * Items can have thair detail - toggled
2123
	 * @param mixed $detail callable|string|bool
2124
	 * @param bool|NULL $primary_where_column
2125
	 * @return Column\ItemDetail
2126
	 */
2127
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2128
	{
2129
		if ($this->isSortable()) {
2130
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2131
		}
2132
2133
		$this->items_detail = new Column\ItemDetail(
2134
			$this,
2135
			$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...
2136
		);
2137
2138
		if (is_string($detail)) {
2139
			/**
2140
			 * Item detail will be in separate template
2141
			 */
2142
			$this->items_detail->setType('template');
2143
			$this->items_detail->setTemplate($detail);
2144
2145
		} else if (is_callable($detail)) {
2146
			/**
2147
			 * Item detail will be rendered via custom callback renderer
2148
			 */
2149
			$this->items_detail->setType('renderer');
2150
			$this->items_detail->setRenderer($detail);
2151
2152
		} else if (TRUE === $detail) {
2153
			/**
2154
			 * Item detail will be rendered probably via block #detail
2155
			 */
2156
			$this->items_detail->setType('block');
2157
2158
		} else {
2159
			throw new DataGridException(
2160
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2161
			);
2162
		}
2163
2164
		return $this->items_detail;
2165
	}
2166
2167
2168
	/********************************************************************************
2169
	 *                                ROW PRIVILEGES                                *
2170
	 ********************************************************************************/
2171
2172
2173
	/**
2174
	 * @param  callable $condition
2175
	 * @return void
2176
	 */
2177
	public function allowRowsGroupAction(callable $condition)
2178
	{
2179
		$this->row_conditions['group_action'] = $condition;
2180
	}
2181
2182
2183
	/**
2184
	 * @param  string   $key
2185
	 * @param  callable $condition
2186
	 * @return void
2187
	 */
2188
	public function allowRowsAction($key, callable $condition)
2189
	{
2190
		$this->row_conditions['action'][$key] = $condition;
2191
	}
2192
2193
2194
	/**
2195
	 * @param  string      $name
2196
	 * @param  string|null $key
2197
	 * @return bool|callable
2198
	 */
2199
	public function getRowCondition($name, $key = NULL)
2200
	{
2201
		if (!isset($this->row_conditions[$name])) {
2202
			return FALSE;
2203
		}
2204
2205
		$condition = $this->row_conditions[$name];
2206
2207
		if (!$key) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $key of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2208
			return $condition;
2209
		}
2210
2211
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2212
	}
2213
2214
2215
	/********************************************************************************
2216
	 *                               COLUMN CALLBACK                                *
2217
	 ********************************************************************************/
2218
2219
2220
	/**
2221
	 * @param  string   $key
2222
	 * @param  callable $callback
2223
	 * @return void
2224
	 */
2225
	public function addColumnCallback($key, callable $callback)
2226
	{
2227
		$this->column_callbacks[$key] = $callback;
2228
	}
2229
2230
2231
	/**
2232
	 * @param  string $key
2233
	 * @return callable|null
2234
	 */
2235
	public function getColumnCallback($key)
2236
	{
2237
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2238
	}
2239
2240
2241
	/********************************************************************************
2242
	 *                                 INLINE EDIT                                  *
2243
	 ********************************************************************************/
2244
2245
2246
	/**
2247
	 * @return InlineEdit
2248
	 */
2249
	public function addInlineEdit($primary_where_column = NULL)
2250
	{
2251
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2252
2253
		return $this->inlineEdit;
2254
	}
2255
2256
2257
	/**
2258
	 * @return InlineEdit|null
2259
	 */
2260
	public function getInlineEdit()
2261
	{
2262
		return $this->inlineEdit;
2263
	}
2264
2265
2266
	public function handleInlineEdit($id)
2267
	{
2268
		if ($this->inlineEdit) {
2269
			$this->inlineEdit->setItemId($id);
2270
2271
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2272
2273
			$this['filter']['inline_edit']->addHidden('_id', $id);
2274
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2275
2276
			$this->redrawItem($id, $primary_where_column);
2277
		}
2278
	}
2279
2280
2281
	/********************************************************************************
2282
	 *                               HIDEABLE COLUMNS                               *
2283
	 ********************************************************************************/
2284
2285
2286
	/**
2287
	 * Can datagrid hide colums?
2288
	 * @return boolean
2289
	 */
2290
	public function canHideColumns()
2291
	{
2292
		return (bool) $this->can_hide_columns;
2293
	}
2294
2295
2296
	/**
2297
	 * Order Grid to set columns hideable.
2298
	 * @return static
2299
	 */
2300
	public function setColumnsHideable()
2301
	{
2302
		$this->can_hide_columns = TRUE;
2303
2304
		return $this;
2305
	}
2306
2307
2308
	/********************************************************************************
2309
	 *                                   INTERNAL                                   *
2310
	 ********************************************************************************/
2311
2312
2313
	/**
2314
	 * Get cont of columns
2315
	 * @return int
2316
	 */
2317
	public function getColumnsCount()
2318
	{
2319
		$count = sizeof($this->getColumns());
2320
2321
		if (!empty($this->actions) || $this->isSortable() || $this->getItemsDetail() || $this->getInlineEdit()) {
2322
			$count++;
2323
		}
2324
2325
		if ($this->hasGroupActions()) {
2326
			$count++;
2327
		}
2328
2329
		return $count;
2330
	}
2331
2332
2333
	/**
2334
	 * Get primary key of datagrid data source
2335
	 * @return string
2336
	 */
2337
	public function getPrimaryKey()
2338
	{
2339
		return $this->primary_key;
2340
	}
2341
2342
2343
	/**
2344
	 * Get set of set columns
2345
	 * @return Column\IColumn[]
2346
	 */
2347
	public function getColumns()
2348
	{
2349
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2350
			$columns_to_hide = [];
2351
2352
			foreach ($this->columns as $key => $column) {
2353
				if ($column->getDefaultHide()) {
2354
					$columns_to_hide[] = $key;
2355
				}
2356
			}
2357
2358
			if (!empty($columns_to_hide)) {
2359
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2360
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2361
			}
2362
		}
2363
2364
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2365
		
2366
		foreach ($hidden_columns as $column) {
2367
			if (!empty($this->columns[$column])) {
2368
				$this->columns_visibility[$column] = [
2369
					'visible' => FALSE,
2370
					'name' => $this->columns[$column]->getName()
2371
				];
2372
2373
				$this->removeColumn($column);
2374
			}
2375
		}
2376
2377
		return $this->columns;
2378
	}
2379
2380
2381
	/**
2382
	 * @return PresenterComponent
2383
	 */
2384
	public function getParent()
2385
	{
2386
		$parent = parent::getParent();
2387
2388
		if (!($parent instanceof PresenterComponent)) {
2389
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2390
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2391
			);
2392
		}
2393
2394
		return $parent;
2395
	}
2396
2397
}
2398