Completed
Push — master ( 98c25c...4f83c9 )
by Pavel
03:26
created

DataGrid   F

Complexity

Total Complexity 376

Size/Duplication

Total Lines 3311
Duplicated Lines 4.47 %

Coupling/Cohesion

Components 5
Dependencies 50

Test Coverage

Coverage 20.15%

Importance

Changes 0
Metric Value
wmc 376
lcom 5
cbo 50
dl 148
loc 3311
ccs 159
cts 789
cp 0.2015
rs 0.5217
c 0
b 0
f 0

148 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 34 1
A attached() 0 13 3
A setRowCallback() 0 6 1
A setPrimaryKey() 0 10 2
A setDataSource() 0 10 1
A getDataSource() 0 8 2
A setTemplateFile() 0 6 1
A getTemplateFile() 0 4 2
A getOriginalTemplateFile() 0 4 1
A useHappyComponents() 0 8 2
A setSortable() 0 10 2
A isSortable() 0 4 1
A setMultiSortEnabled() 0 4 1
A isMultiSortEnabled() 0 4 1
A setSortableHandler() 0 6 1
A getSortableHandler() 0 4 1
A isTreeView() 0 4 1
A hasTreeViewChildrenCallback() 0 4 1
A treeViewChildrenCallback() 0 4 1
A addColumnText() 0 7 2
A addColumnNumber() 0 7 2
A addColumnDateTime() 0 7 2
A addColumnStatus() 0 7 2
A getColumn() 0 8 2
A removeColumn() 0 5 1
A addColumnCheck() 0 6 2
A addMultiAction() 0 8 1
A getAction() 8 8 2
A removeAction() 0 4 1
A addActionCheck() 0 6 2
A addFilterText() 0 12 4
A addFilterSelect() 12 12 3
A addFilterMultiSelect() 12 12 3
A addFilterDate() 12 12 3
A addFilterRange() 12 12 3
A addFilterDateRange() 12 12 3
A addFilterCheck() 0 6 2
A removeFilter() 0 4 1
A getFilter() 0 8 2
A setStrictSessionFilterValues() 0 4 1
A isFilterActive() 0 6 2
A setFilterActive() 0 6 1
A setFilter() 0 8 1
A setOuterFilterRendering() 0 6 1
A hasOuterFilterRendering() 0 4 1
A setCollapsibleOuterFilters() 0 4 1
A hasCollapsibleOuterFilters() 0 4 1
A addExportCallback() 0 8 2
A addToExports() 0 8 2
A addToolbarButton() 0 6 1
A addGroupAction() 0 4 1
A addGroupSelectAction() 0 4 1
D render() 0 103 12
A setDefaultSort() 0 13 2
A findDefaultSort() 0 16 4
A getSortNext() 0 10 2
B createSorting() 0 21 5
B setTreeView() 0 30 4
A addColumnLink() 0 12 4
A addColumn() 0 10 1
A addAction() 0 12 3
A addActionCallback() 0 18 3
D assableFilters() 0 33 10
D setDefaultFilter() 0 38 9
B findDefaultFilter() 0 18 5
F createComponentFilter() 17 86 9
C setFilterContainerDefaults() 0 29 8
D filterSucceeded() 0 106 25
D findSessionValues() 0 68 20
A addExportCsv() 12 12 1
A addExportCsvFiltered() 12 12 1
A resetExportsLinks() 0 6 2
A addGroupMultiSelectAction() 0 4 1
A addGroupTextAction() 0 4 1
A addGroupTextareaAction() 0 4 1
A getGroupActionCollection() 0 8 2
A hasGroupActions() 0 4 1
A handlePage() 0 10 1
B handleSort() 0 24 5
B handleResetFilter() 0 34 5
A handleResetColumnFilter() 0 7 1
A setColumnReset() 0 6 1
A hasColumnReset() 0 4 1
A sendNonEmptyFiltersInPayload() 0 16 4
B handleExport() 0 53 8
A handleGetChildren() 0 17 2
A handleGetItemDetail() 0 21 3
A handleEdit() 0 7 1
B reload() 0 24 3
A handleChangeStatus() 0 8 2
A redrawItem() 0 12 2
A handleShowAllColumns() 0 9 1
A handleShowDefaultColumns() 0 9 1
A handleShowColumn() 19 19 3
A handleHideColumn() 20 20 3
A handleActionCallback() 0 10 2
A setItemsPerPageList() 0 10 2
A setDefaultPerPage() 0 6 1
A findDefaultPerPage() 0 12 3
A createComponentPaginator() 0 16 1
A getPerPage() 0 12 4
A getItemsPerPageList() 0 18 4
A setPagination() 0 6 1
A isPaginated() 0 4 1
A getPaginator() 0 8 3
A setTranslator() 0 6 1
A getTranslator() 0 8 2
A setColumnsOrder() 0 18 4
A setColumnsExportOrder() 0 4 1
A getSessionSectionName() 0 4 1
A setRememberState() 0 6 1
A setRefreshUrl() 0 7 1
B getSessionData() 0 8 5
A saveSessionData() 0 6 2
A deleteSessionData() 0 4 1
A deleteSesssionData() 0 5 1
A getItemsDetail() 0 4 1
B setItemsDetail() 0 39 6
A setItemsDetailForm() 0 12 2
A getItemDetailForm() 0 8 2
A allowRowsGroupAction() 0 4 1
A allowRowsInlineEdit() 0 4 1
A allowRowsAction() 0 4 1
A getRowCondition() 0 14 4
A addColumnCallback() 0 4 1
A getColumnCallback() 0 4 2
A addInlineEdit() 0 6 2
A getInlineEdit() 0 4 1
A handleInlineEdit() 0 17 3
A addInlineAdd() 0 11 1
A getInlineAdd() 0 4 1
A canHideColumns() 0 4 1
A setColumnsHideable() 0 6 1
A hasColumnsSummary() 0 4 1
A setColumnsSummary() 0 16 4
A getColumnsSummary() 0 4 1
A setAutoSubmit() 0 6 1
A hasAutoSubmit() 0 4 1
A getFilterSubmitButton() 0 14 3
B getColumnsCount() 0 18 7
A getPrimaryKey() 0 4 1
C getColumns() 0 39 8
A getColumnsVisibility() 0 10 2
A getParent() 0 12 2
A getSortableParentPath() 0 4 1
A setSomeColumnDefaultHide() 0 4 1
A hasSomeColumnDefaultHide() 0 4 1
A handleRefreshState() 0 10 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like DataGrid often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DataGrid, and based on these observations, apply Extract Interface, too.

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\Link;
13
use Nette\Application\UI\PresenterComponent;
14
use Ublaboo\DataGrid\Utils\ArraysHelper;
15
use Nette\Application\UI\Form;
16
use Ublaboo\DataGrid\Exception\DataGridException;
17
use Ublaboo\DataGrid\Exception\DataGridColumnNotFoundException;
18
use Ublaboo\DataGrid\Exception\DataGridFilterNotFoundException;
19
use Ublaboo\DataGrid\Exception\DataGridHasToBeAttachedToPresenterComponentException;
20
use Ublaboo\DataGrid\Filter\IFilterDate;
21
use Ublaboo\DataGrid\Utils\DateTimeHelper;
22
use Ublaboo\DataGrid\Utils\Sorting;
23
use Ublaboo\DataGrid\InlineEdit\InlineEdit;
24
use Ublaboo\DataGrid\ColumnsSummary;
25
use Ublaboo\DataGrid\Toolbar\ToolbarButton;
26
use Ublaboo\DataGrid\AggregationFunction\TDataGridAggregationFunction;
27
28
/**
29
 * @method onRedraw()
30
 * @method onRender()
31
 * @method onColumnAdd()
32
 */
33 1
class DataGrid extends Nette\Application\UI\Control
34
{
35
36 1
	use TDataGridAggregationFunction;
37
38
	/**
39
	 * @var callable[]
40
	 */
41
	public $onRedraw;
42
43
	/**
44
	 * @var callable[]
45
	 */
46
	public $onRender = [];
47
48
	/**
49
	 * @var callable[]
50
	 */
51
	public $onExport;
52
53
	/**
54
	 * @var callable[]
55
	 */
56
	public $onColumnAdd;
57
58
	/**
59
	 * @var callable[]
60
	 */
61
	public $onFiltersAssabled;
62
63
	/**
64
	 * @var string
65
	 */
66
	public static $icon_prefix = 'fa fa-';
67
68
	/**
69
	 * Default form method
70
	 * @var string
71
	 */
72
	public static $form_method = 'post';
73
74
	/**
75
	 * When set to TRUE, datagrid throws an exception
76
	 * 	when tring to get related entity within join and entity does not exist
77
	 * @var bool
78
	 */
79
	public $strict_entity_property = FALSE;
80
81
	/**
82
	 * When set to TRUE, datagrid throws an exception
83
	 * 	when tring to set filter value, that does not exist (select, multiselect, etc)
84
	 * @var bool
85
	 */
86
	public $strict_session_filter_values = TRUE;
87
88
	/**
89
	 * @var int
90
	 * @persistent
91
	 */
92
	public $page = 1;
93
94
	/**
95
	 * @var int|string
96
	 * @persistent
97
	 */
98
	public $per_page;
99
100
	/**
101
	 * @var array
102
	 * @persistent
103
	 */
104
	public $sort = [];
105
106
	/**
107
	 * @var array
108
	 */
109
	public $default_sort = [];
110
111
	/**
112
	 * @var array
113
	 */
114
	public $default_filter = [];
115
116
	/**
117
	 * @var bool
118
	 */
119
	public $default_filter_use_on_reset = TRUE;
120
121
	/**
122
	 * @var bool
123
	 */
124
	public $default_sort_use_on_reset = TRUE;
125
126
	/**
127
	 * @var array
128
	 * @persistent
129
	 */
130
	public $filter = [];
131
132
	/**
133
	 * @var callable|null
134
	 */
135
	protected $sort_callback = NULL;
136
137
	/**
138
	 * @var bool
139
	 */
140
	protected $use_happy_components = TRUE;
141
142
	/**
143
	 * @var callable
144
	 */
145
	protected $rowCallback;
146
147
	/**
148
	 * @var array
149
	 */
150
	protected $items_per_page_list;
151
152
	/**
153
	 * @var int
154
	 */
155
	protected $default_per_page;
156
157
	/**
158
	 * @var string
159
	 */
160
	protected $template_file;
161
162
	/**
163
	 * @var Column\IColumn[]
164
	 */
165
	protected $columns = [];
166
167
	/**
168
	 * @var Column\Action[]
169
	 */
170
	protected $actions = [];
171
172
	/**
173
	 * @var GroupAction\GroupActionCollection
174
	 */
175
	protected $group_action_collection;
176
177
	/**
178
	 * @var Filter\Filter[]
179
	 */
180
	protected $filters = [];
181
182
	/**
183
	 * @var Export\Export[]
184
	 */
185
	protected $exports = [];
186
187
	/**
188
	 * @var ToolbarButton[]
189
	 */
190
	protected $toolbar_buttons = [];
191
192
	/**
193
	 * @var DataModel
194
	 */
195
	protected $dataModel;
196
197
	/**
198
	 * @var DataFilter
199
	 */
200
	protected $dataFilter;
201
202
	/**
203
	 * @var string
204
	 */
205
	protected $primary_key = 'id';
206
207
	/**
208
	 * @var bool
209
	 */
210
	protected $do_paginate = TRUE;
211
212
	/**
213
	 * @var bool
214
	 */
215
	protected $csv_export = TRUE;
216
217
	/**
218
	 * @var bool
219
	 */
220
	protected $csv_export_filtered = TRUE;
221
222
	/**
223
	 * @var bool
224
	 */
225
	protected $sortable = FALSE;
226
227
	/**
228
	 * @var bool
229
	 */
230
	protected $multiSort = FALSE;
231
232
	/**
233
	 * @var string
234
	 */
235
	protected $sortable_handler = 'sort!';
236
237
	/**
238
	 * @var string
239
	 */
240
	protected $original_template;
241
242
	/**
243
	 * @var array
244
	 */
245
	protected $redraw_item;
246
247
	/**
248
	 * @var mixed
249
	 */
250
	protected $translator;
251
252
	/**
253
	 * @var bool
254
	 */
255
	protected $force_filter_active;
256
257
	/**
258
	 * @var callable
259
	 */
260
	protected $tree_view_children_callback;
261
262
	/**
263
	 * @var callable
264
	 */
265
	protected $tree_view_has_children_callback;
266
267
	/**
268
	 * @var string
269
	 */
270
	protected $tree_view_has_children_column;
271
272
	/**
273
	 * @var bool
274
	 */
275
	protected $outer_filter_rendering = FALSE;
276
277
	/**
278
	 * @var bool
279
	 */
280
	protected $collapsible_outer_filters = TRUE;
281
282
	/**
283
	 * @var array
284
	 */
285
	protected $columns_export_order = [];
286
287
	/**
288
	 * @var bool
289
	 */
290
	protected $remember_state = TRUE;
291
292
	/**
293
	 * @var bool
294
	 */
295
	protected $refresh_url = TRUE;
296
297
	/**
298
	 * @var Nette\Http\SessionSection
299
	 */
300
	protected $grid_session;
301
302
	/**
303
	 * @var Column\ItemDetail
304
	 */
305
	protected $items_detail;
306
307
	/**
308
	 * @var array
309
	 */
310
	protected $row_conditions = [
311
		'group_action' => FALSE,
312
		'action' => []
313
	];
314
315
	/**
316
	 * @var array
317
	 */
318
	protected $column_callbacks = [];
319
320
	/**
321
	 * @var bool
322
	 */
323
	protected $can_hide_columns = FALSE;
324
325
	/**
326
	 * @var array
327
	 */
328
	protected $columns_visibility = [];
329
330
	/**
331
	 * @var InlineEdit
332
	 */
333
	protected $inlineEdit;
334
335
	/**
336
	 * @var InlineEdit
337
	 */
338
	protected $inlineAdd;
339
340
	/**
341
	 * @var bool
342
	 */
343
	protected $snippets_set = FALSE;
344
345
	/**
346
	 * @var bool
347
	 */
348
	protected $some_column_default_hide = FALSE;
349
350
	/**
351
	 * @var ColumnsSummary
352
	 */
353
	protected $columnsSummary;
354
355
	/**
356
	 * @var bool
357
	 */
358
	protected $auto_submit = TRUE;
359
360
	/**
361
	 * @var Filter\SubmitButton|NULL
362
	 */
363
	protected $filter_submit_button = NULL;
364
365
	/**
366
	 * @var bool
367
	 */
368
	protected $has_column_reset = TRUE;
369
370
371
	/**
372
	 * @param Nette\ComponentModel\IContainer|NULL $parent
373
	 * @param string                               $name
374
	 */
375
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
376
	{
377 1
		parent::__construct($parent, $name);
378
379 1
		$this->monitor('Nette\Application\UI\Presenter');
380
381
		/**
382
		 * Try to find previous filters, pagination, per_page and other values in session
383
		 */
384 1
		$this->onRender[] = [$this, 'findSessionValues'];
385 1
		$this->onExport[] = [$this, 'findSessionValues'];
386
387
		/**
388
		 * Find default filter values
389
		 */
390 1
		$this->onRender[] = [$this, 'findDefaultFilter'];
391 1
		$this->onExport[] = [$this, 'findDefaultFilter'];
392
393
		/**
394
		 * Find default sort
395
		 */
396 1
		$this->onRender[] = [$this, 'findDefaultSort'];
397 1
		$this->onExport[] = [$this, 'findDefaultSort'];
398
399
		/**
400
		 * Find default items per page
401
		 */
402 1
		$this->onRender[] = [$this, 'findDefaultPerPage'];
403
404
		/**
405
		 * Notify about that json js extension
406
		 */
407 1
		$this->onFiltersAssabled[] = [$this, 'sendNonEmptyFiltersInPayload'];
408 1
	}
409
410
411
	/**
412
	 * {inheritDoc}
413
	 * @return void
414
	 */
415
	public function attached($presenter)
416
	{
417 1
		parent::attached($presenter);
418
419 1
		if ($presenter instanceof Nette\Application\UI\Presenter) {
420
			/**
421
			 * Get session
422
			 */
423 1
			if ($this->remember_state) {
424 1
				$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...
425
			}
426
		}
427 1
	}
428
429
430
	/********************************************************************************
431
	 *                                  RENDERING                                   *
432
	 ********************************************************************************/
433
434
435
	/**
436
	 * Render template
437
	 * @return void
438
	 */
439
	public function render()
440
	{
441
		/**
442
		 * Check whether datagrid has set some columns, initiated data source, etc
443
		 */
444
		if (!($this->dataModel instanceof DataModel)) {
445
			throw new DataGridException('You have to set a data source first.');
446
		}
447
448
		if (empty($this->columns)) {
449
			throw new DataGridException('You have to add at least one column.');
450
		}
451
452
		$this->getTemplate()->setTranslator($this->getTranslator());
453
454
		/**
455
		 * Invoke possible events
456
		 */
457
		$this->onRender($this);
0 ignored issues
show
Unused Code introduced by
The call to DataGrid::onRender() has too many arguments starting with $this.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
458
459
		/**
460
		 * Prepare data for rendering (datagrid may render just one item)
461
		 */
462
		$rows = [];
463
464
		if (!empty($this->redraw_item)) {
465
			$items = $this->dataModel->filterRow($this->redraw_item);
466
		} else {
467
			$items = Nette\Utils\Callback::invokeArgs(
468
				[$this->dataModel, 'filterData'],
469
				[
470
					$this->getPaginator(),
471
					$this->createSorting($this->sort, $this->sort_callback),
472
					$this->assableFilters()
473
				]
474
			);
475
		}
476
477
		$callback = $this->rowCallback ?: NULL;
478
		$hasGroupActionOnRows = FALSE;
479
480
		foreach ($items as $item) {
481
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
482
483
			if (!$hasGroupActionOnRows && $row->hasGroupAction()){
484
				$hasGroupActionOnRows = TRUE;
485
			}
486
			
487
			if ($callback) {
488
				$callback($item, $row->getControl());
489
			}
490
491
			/**
492
			 * Walkaround for item snippet - snippet is the <tr> element and its class has to be also updated
493
			 */
494
			if (!empty($this->redraw_item)) {
495
				$this->getPresenter()->payload->_datagrid_redraw_item_class = $row->getControlClass();
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...
496
				$this->getPresenter()->payload->_datagrid_redraw_item_id = $row->getId();
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...
497
			}
498
		}
499
500
		if ($hasGroupActionOnRows){
501
			$hasGroupActionOnRows = $this->hasGroupActions();
502
		}
503
504
		if ($this->isTreeView()) {
505
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
506
		}
507
508
		$this->getTemplate()->add('rows', $rows);
509
510
		$this->getTemplate()->add('columns', $this->getColumns());
511
		$this->getTemplate()->add('actions', $this->actions);
512
		$this->getTemplate()->add('exports', $this->exports);
513
		$this->getTemplate()->add('filters', $this->filters);
514
		$this->getTemplate()->add('toolbar_buttons', $this->toolbar_buttons);
515
		$this->getTemplate()->add('aggregation_functions', $this->getAggregationFunctions());
516
		$this->getTemplate()->add('multiple_aggregation_function', $this->getMultipleAggregationFunction());
517
518
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
519
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
520
		//$this->getTemplate()->add('icon_prefix', static::$icon_prefix);
0 ignored issues
show
Unused Code Comprehensibility introduced by
82% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
521
		$this->getTemplate()->icon_prefix = static::$icon_prefix;
0 ignored issues
show
Bug introduced by
Accessing icon_prefix on the interface Nette\Application\UI\ITemplate suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
522
		$this->getTemplate()->add('items_detail', $this->items_detail);
523
		$this->getTemplate()->add('columns_visibility', $this->getColumnsVisibility());
524
		$this->getTemplate()->add('columnsSummary', $this->columnsSummary);
525
526
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
527
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
528
529
		$this->getTemplate()->add('hasGroupActionOnRows', $hasGroupActionOnRows);
530
531
		/**
532
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
533
		 */
534
		$this->getTemplate()->add('filter', $this['filter']);
535
536
		/**
537
		 * Set template file and render it
538
		 */
539
		$this->getTemplate()->setFile($this->getTemplateFile());
540
		$this->getTemplate()->render();
541
	}
542
543
544
	/********************************************************************************
545
	 *                                 ROW CALLBACK                                 *
546
	 ********************************************************************************/
547
548
549
	/**
550
	 * Each row can be modified with user callback
551
	 * @param  callable  $callback
552
	 * @return static
553
	 */
554
	public function setRowCallback(callable $callback)
555
	{
556
		$this->rowCallback = $callback;
557
558
		return $this;
559
	}
560
561
562
	/********************************************************************************
563
	 *                                 DATA SOURCE                                  *
564
	 ********************************************************************************/
565
566
567
	/**
568
	 * By default ID, you can change that
569
	 * @param string $primary_key
570
	 * @return static
571
	 */
572
	public function setPrimaryKey($primary_key)
573
	{
574
		if ($this->dataModel instanceof DataModel) {
575
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
576
		}
577
578
		$this->primary_key = $primary_key;
579
580
		return $this;
581
	}
582
583
584
	/**
585
	 * Set Grid data source
586
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
587
	 * @return static
588
	 */
589
	public function setDataSource($source)
590
	{
591 1
		$this->dataModel = new DataModel($source, $this->primary_key);
592
593 1
		$this->dataModel->onBeforeFilter[] = [$this, 'beforeDataModelFilter'];
594 1
		$this->dataModel->onAfterFilter[] = [$this, 'afterDataModelFilter'];
595 1
		$this->dataModel->onAfterPaginated[] = [$this, 'afterDataModelPaginated'];
596
597 1
		return $this;
598
	}
599
600
601
	/**
602
	 * @return DataSource\IDataSource|NULL
603
	 */
604
	public function getDataSource()
605
	{
606
		if (!$this->dataModel) {
607
			return NULL;
608
		}
609
610
		return $this->dataModel->getDataSource();
611
	}
612
613
614
	/********************************************************************************
615
	 *                                  TEMPLATING                                  *
616
	 ********************************************************************************/
617
618
619
	/**
620
	 * Set custom template file to render
621
	 * @param string $template_file
622
	 * @return static
623
	 */
624
	public function setTemplateFile($template_file)
625
	{
626
		$this->template_file = $template_file;
627
628
		return $this;
629
	}
630
631
632
	/**
633
	 * Get DataGrid template file
634
	 * @return string
635
	 * @return static
636
	 */
637
	public function getTemplateFile()
638
	{
639
		return $this->template_file ?: $this->getOriginalTemplateFile();
640
	}
641
642
643
	/**
644
	 * Get DataGrid original template file
645
	 * @return string
646
	 */
647
	public function getOriginalTemplateFile()
648
	{
649
		return __DIR__.'/templates/datagrid.latte';
650
	}
651
652
653
	/**
654
	 * Tell datagrid wheteher to use or not happy components
655
	 * @param  bool|NULL $use If not given, return value of static::$use_happy_components
656
	 * @return void|bool
657
	 */
658
	public function useHappyComponents($use = NULL)
659
	{
660
		if (NULL === $use) {
661
			return $this->use_happy_components;
662
		}
663
664
		$this->use_happy_components = (bool) $use;
665
	}
666
667
668
	/********************************************************************************
669
	 *                                   SORTING                                    *
670
	 ********************************************************************************/
671
672
673
	/**
674
	 * Set default sorting
675
	 * @param array $sort
676
	 * @param bool  $use_on_reset
677
	 * @return static
678
	 */
679
	public function setDefaultSort($sort, $use_on_reset = TRUE)
680
	{
681
		if (is_string($sort)) {
682
			$sort = [$sort => 'ASC'];
683
		} else {
684
			$sort = (array) $sort;
685
		}
686
687
		$this->default_sort = $sort;
688
		$this->default_sort_use_on_reset = (bool) $use_on_reset;
689
690
		return $this;
691
	}
692
693
694
	/**
695
	 * User may set default sorting, apply it
696
	 * @return void
697
	 */
698
	public function findDefaultSort()
699
	{
700 1
		if (!empty($this->sort)) {
701
			return;
702
		}
703
704 1
		if ($this->getSessionData('_grid_has_sorted')) {
705
			return;
706
		}
707
708 1
		if (!empty($this->default_sort)) {
709
			$this->sort = $this->default_sort;
710
		}
711
712 1
		$this->saveSessionData('_grid_sort', $this->sort);
713 1
	}
714
715
716
	/**
717
	 * Set grido to be sortable
718
	 * @param bool $sortable
719
	 * @return static
720
	 */
721
	public function setSortable($sortable = TRUE)
722
	{
723
		if ($this->getItemsDetail()) {
724
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
725
		}
726
727
		$this->sortable = (bool) $sortable;
728
729
		return $this;
730
	}
731
732
733
	/**
734
	 * Tell whether DataGrid is sortable
735
	 * @return bool
736
	 */
737
	public function isSortable()
738
	{
739
		return $this->sortable;
740
	}
741
742
743
	/**
744
	 * Enable multi-sorting capability
745
	 * @param bool  $multiSort
746
	 * @return void
747
	 */
748
	public function setMultiSortEnabled($multiSort = TRUE)
749
	{
750
		$this->multiSort = (bool) $multiSort;
751
	}
752
753
754
	/**
755
	 * Tell wether DataGrid can be sorted by multiple columns
756
	 * @return bool
757
	 */
758
	public function isMultiSortEnabled()
759
	{
760
		return $this->multiSort;
761
	}
762
763
764
	/**
765
	 * Set sortable handle
766
	 * @param string $handler
767
	 * @return static
768
	 */
769
	public function setSortableHandler($handler = 'sort!')
770
	{
771
		$this->sortable_handler = (string) $handler;
772
773
		return $this;
774
	}
775
776
	/**
777
	 * Return sortable handle name
778
	 * @return string
779
	 */
780
	public function getSortableHandler()
781
	{
782
		return $this->sortable_handler;
783
	}
784
785
786
	/**
787
	 * @param Column  $column
788
	 * @return array
789
	 * @internal
790
	 */
791
	public function getSortNext(\Ublaboo\DataGrid\Column\Column $column)
792
	{
793
		$sort = $column->getSortNext();
794
795
		if ($this->isMultiSortEnabled()) {
796
			$sort = array_merge($this->sort, $sort);
797
		}
798
799
		return array_filter($sort);
800
	}
801
802
803
	/**
804
	 * @param  array         $sort
805
	 * @param  callable|NULL $sort_callback
806
	 * @return Sorting
807
	 */
808
	protected function createSorting(array $sort, callable $sort_callback = NULL)
809
	{
810 1
		foreach ($sort as $key => $order) {
811
			unset($sort[$key]);
812
813
			try {
814
				$column = $this->getColumn($key);
815
816
			} catch (DataGridColumnNotFoundException $e) {
817
				continue;
818
			}
819
820
			$sort[$column->getSortingColumn()] = $order;
821
		}
822
823 1
		if (!$sort_callback && isset($column)) {
824
			$sort_callback = $column->getSortableCallback();
825
		}
826
827 1
		return new Sorting($sort, $sort_callback);
828
	}
829
830
831
	/********************************************************************************
832
	 *                                  TREE VIEW                                   *
833
	 ********************************************************************************/
834
835
836
	/**
837
	 * Is tree view set?
838
	 * @return bool
839
	 */
840
	public function isTreeView()
841
	{
842
		return (bool) $this->tree_view_children_callback;
843
	}
844
845
846
	/**
847
	 * Setting tree view
848
	 * @param callable $get_children_callback
849
	 * @param string|callable $tree_view_has_children_column
850
	 * @return static
851
	 */
852
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
853
	{
854
		if (!is_callable($get_children_callback)) {
855
			throw new DataGridException(
856
				'Parameters to method DataGrid::setTreeView must be of type callable'
857
			);
858
		}
859
860
		if (is_callable($tree_view_has_children_column)) {
861
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
862
			$tree_view_has_children_column = NULL;
863
		}
864
865
		$this->tree_view_children_callback = $get_children_callback;
866
		$this->tree_view_has_children_column = $tree_view_has_children_column;
867
868
		/**
869
		 * TUrn off pagination
870
		 */
871
		$this->setPagination(FALSE);
872
873
		/**
874
		 * Set tree view template file
875
		 */
876
		if (!$this->template_file) {
877
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
878
		}
879
880
		return $this;
881
	}
882
883
884
	/**
885
	 * Is tree view children callback set?
886
	 * @return bool
887
	 */
888
	public function hasTreeViewChildrenCallback()
889
	{
890
		return is_callable($this->tree_view_has_children_callback);
891
	}
892
893
894
	/**
895
	 * @param  mixed $item
896
	 * @return bool
897
	 */
898
	public function treeViewChildrenCallback($item)
899
	{
900
		return call_user_func($this->tree_view_has_children_callback, $item);
901
	}
902
903
904
	/********************************************************************************
905
	 *                                    COLUMNS                                   *
906
	 ********************************************************************************/
907
908
909
	/**
910
	 * Add text column with no other formating
911
	 * @param  string      $key
912
	 * @param  string      $name
913
	 * @param  string|null $column
914
	 * @return Column\ColumnText
915
	 */
916
	public function addColumnText($key, $name, $column = NULL)
917
	{
918 1
		$this->addColumnCheck($key);
919 1
		$column = $column ?: $key;
920
921 1
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
922
	}
923
924
925
	/**
926
	 * Add column with link
927
	 * @param  string      $key
928
	 * @param  string      $name
929
	 * @param  string|null $column
930
	 * @return Column\ColumnLink
931
	 */
932
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
933
	{
934 1
		$this->addColumnCheck($key);
935 1
		$column = $column ?: $key;
936 1
		$href = $href ?: $key;
937
938 1
		if (NULL === $params) {
939 1
			$params = [$this->primary_key];
940
		}
941
942 1
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
943
	}
944
945
946
	/**
947
	 * Add column with possible number formating
948
	 * @param  string      $key
949
	 * @param  string      $name
950
	 * @param  string|null $column
951
	 * @return Column\ColumnNumber
952
	 */
953
	public function addColumnNumber($key, $name, $column = NULL)
954
	{
955 1
		$this->addColumnCheck($key);
956 1
		$column = $column ?: $key;
957
958 1
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
959
	}
960
961
962
	/**
963
	 * Add column with date formating
964
	 * @param  string      $key
965
	 * @param  string      $name
966
	 * @param  string|null $column
967
	 * @return Column\ColumnDateTime
968
	 */
969
	public function addColumnDateTime($key, $name, $column = NULL)
970
	{
971 1
		$this->addColumnCheck($key);
972 1
		$column = $column ?: $key;
973
974 1
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
975
	}
976
977
978
	/**
979
	 * Add column status
980
	 * @param  string      $key
981
	 * @param  string      $name
982
	 * @param  string|null $column
983
	 * @return Column\ColumnStatus
984
	 */
985
	public function addColumnStatus($key, $name, $column = NULL)
986
	{
987 1
		$this->addColumnCheck($key);
988 1
		$column = $column ?: $key;
989
990 1
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
991
	}
992
993
994
	/**
995
	 * @param string $key
996
	 * @param Column\Column $column
997
	 * @return Column\Column
998
	 */
999
	protected function addColumn($key, Column\Column $column)
1000
	{
1001 1
		$this->onColumnAdd($key, $column);
0 ignored issues
show
Unused Code introduced by
The call to DataGrid::onColumnAdd() has too many arguments starting with $key.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1002
1003 1
		$this->columns_visibility[$key] = [
1004
			'visible' => TRUE
1005
		];
1006
1007 1
		return $this->columns[$key] = $column;
1008
	}
1009
1010
1011
	/**
1012
	 * Return existing column
1013
	 * @param  string $key
1014
	 * @return Column\Column
1015
	 * @throws DataGridException
1016
	 */
1017
	public function getColumn($key)
1018
	{
1019 1
		if (!isset($this->columns[$key])) {
1020
			throw new DataGridColumnNotFoundException("There is no column at key [$key] defined.");
1021
		}
1022
1023 1
		return $this->columns[$key];
1024
	}
1025
1026
1027
	/**
1028
	 * Remove column
1029
	 * @param string $key
1030
	 * @return void
1031
	 */
1032
	public function removeColumn($key)
1033
	{
1034 1
		unset($this->columns_visibility[$key]);
1035 1
		unset($this->columns[$key]);
1036 1
	}
1037
1038
1039
	/**
1040
	 * Check whether given key already exists in $this->columns
1041
	 * @param  string $key
1042
	 * @throws DataGridException
1043
	 */
1044
	protected function addColumnCheck($key)
1045
	{
1046 1
		if (isset($this->columns[$key])) {
1047
			throw new DataGridException("There is already column at key [$key] defined.");
1048
		}
1049 1
	}
1050
1051
1052
	/********************************************************************************
1053
	 *                                    ACTIONS                                   *
1054
	 ********************************************************************************/
1055
1056
1057
	/**
1058
	 * Create action
1059
	 * @param string     $key
1060
	 * @param string     $name
1061
	 * @param string     $href
1062
	 * @param array|null $params
1063
	 * @return Column\Action
1064
	 */
1065
	public function addAction($key, $name, $href = NULL, array $params = NULL)
1066
	{
1067 1
		$this->addActionCheck($key);
1068
1069 1
		$href = $href ?: $key;
1070
1071 1
		if (NULL === $params) {
1072 1
			$params = [$this->primary_key];
1073
		}
1074
1075 1
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
1076
	}
1077
1078
1079
	/**
1080
	 * Create action callback
1081
	 * @param string     $key
1082
	 * @param string     $name
1083
	 * @return Column\Action
1084
	 */
1085
	public function addActionCallback($key, $name, $callback = NULL)
1086
	{
1087
		$this->addActionCheck($key);
1088
1089
		$params = ['__id' => $this->primary_key];
1090
1091
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
1092
1093
		if ($callback) {
1094
			if (!is_callable($callback)) {
1095
				throw new DataGridException('ActionCallback callback has to be callable.');
1096
			}
1097
1098
			$action->onClick[] = $callback;
1099
		}
1100
1101
		return $action;
1102
	}
1103
1104
1105
	/**
1106
	 * @param string $key
1107
	 */
1108
	public function addMultiAction($key, $name)
1109
	{
1110
		$this->addActionCheck($key);
1111
1112
		$this->actions[$key] = $action = new Column\MultiAction($this, $name);
1113
1114
		return $action;
1115
	}
1116
1117
1118
	/**
1119
	 * Get existing action
1120
	 * @param  string       $key
1121
	 * @return Column\Action
1122
	 * @throws DataGridException
1123
	 */
1124 View Code Duplication
	public function getAction($key)
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...
1125
	{
1126
		if (!isset($this->actions[$key])) {
1127
			throw new DataGridException("There is no action at key [$key] defined.");
1128
		}
1129
1130
		return $this->actions[$key];
1131
	}
1132
1133
1134
	/**
1135
	 * Remove action
1136
	 * @param string $key
1137
	 * @return void
1138
	 */
1139
	public function removeAction($key)
1140
	{
1141
		unset($this->actions[$key]);
1142
	}
1143
1144
1145
	/**
1146
	 * Check whether given key already exists in $this->filters
1147
	 * @param  string $key
1148
	 * @throws DataGridException
1149
	 */
1150
	protected function addActionCheck($key)
1151
	{
1152 1
		if (isset($this->actions[$key])) {
1153 1
			throw new DataGridException("There is already action at key [$key] defined.");
1154
		}
1155 1
	}
1156
1157
1158
	/********************************************************************************
1159
	 *                                    FILTERS                                   *
1160
	 ********************************************************************************/
1161
1162
1163
	/**
1164
	 * Add filter fot text search
1165
	 * @param string       $key
1166
	 * @param string       $name
1167
	 * @param array|string $columns
1168
	 * @return Filter\FilterText
1169
	 * @throws DataGridException
1170
	 */
1171
	public function addFilterText($key, $name, $columns = NULL)
1172
	{
1173 1
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
1174
1175 1
		if (!is_array($columns)) {
1176
			throw new DataGridException("Filter Text can accept only array or string.");
1177
		}
1178
1179 1
		$this->addFilterCheck($key);
1180
1181 1
		return $this->filters[$key] = new Filter\FilterText($this, $key, $name, $columns);
1182
	}
1183
1184
1185
	/**
1186
	 * Add select box filter
1187
	 * @param string $key
1188
	 * @param string $name
1189
	 * @param array  $options
1190
	 * @param string $column
1191
	 * @return Filter\FilterSelect
1192
	 * @throws DataGridException
1193
	 */
1194 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...
1195
	{
1196
		$column = $column ?: $key;
1197
1198
		if (!is_string($column)) {
1199
			throw new DataGridException("Filter Select can only filter in one column.");
1200
		}
1201
1202
		$this->addFilterCheck($key);
1203
1204
		return $this->filters[$key] = new Filter\FilterSelect($this, $key, $name, $options, $column);
1205
	}
1206
1207
1208
	/**
1209
	 * Add multi select box filter
1210
	 * @param string $key
1211
	 * @param string $name
1212
	 * @param array  $options
1213
	 * @param string $column
1214
	 * @return Filter\FilterSelect
1215
	 * @throws DataGridException
1216
	 */
1217 View Code Duplication
	public function addFilterMultiSelect($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...
1218
	{
1219
		$column = $column ?: $key;
1220
1221
		if (!is_string($column)) {
1222
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1223
		}
1224
1225
		$this->addFilterCheck($key);
1226
1227
		return $this->filters[$key] = new Filter\FilterMultiSelect($this, $key, $name, $options, $column);
1228
	}
1229
1230
1231
	/**
1232
	 * Add datepicker filter
1233
	 * @param string $key
1234
	 * @param string $name
1235
	 * @param string $column
1236
	 * @return Filter\FilterDate
1237
	 * @throws DataGridException
1238
	 */
1239 View Code Duplication
	public function addFilterDate($key, $name, $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...
1240
	{
1241
		$column = $column ?: $key;
1242
1243
		if (!is_string($column)) {
1244
			throw new DataGridException("FilterDate can only filter in one column.");
1245
		}
1246
1247
		$this->addFilterCheck($key);
1248
1249
		return $this->filters[$key] = new Filter\FilterDate($this, $key, $name, $column);
1250
	}
1251
1252
1253
	/**
1254
	 * Add range filter (from - to)
1255
	 * @param string $key
1256
	 * @param string $name
1257
	 * @param string $column
1258
	 * @return Filter\FilterRange
1259
	 * @throws DataGridException
1260
	 */
1261 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...
1262
	{
1263
		$column = $column ?: $key;
1264
1265
		if (!is_string($column)) {
1266
			throw new DataGridException("FilterRange can only filter in one column.");
1267
		}
1268
1269
		$this->addFilterCheck($key);
1270
1271
		return $this->filters[$key] = new Filter\FilterRange($this, $key, $name, $column, $name_second);
1272
	}
1273
1274
1275
	/**
1276
	 * Add datepicker filter (from - to)
1277
	 * @param string $key
1278
	 * @param string $name
1279
	 * @param string $column
1280
	 * @return Filter\FilterDateRange
1281
	 * @throws DataGridException
1282
	 */
1283 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...
1284
	{
1285
		$column = $column ?: $key;
1286
1287
		if (!is_string($column)) {
1288
			throw new DataGridException("FilterDateRange can only filter in one column.");
1289
		}
1290
1291
		$this->addFilterCheck($key);
1292
1293
		return $this->filters[$key] = new Filter\FilterDateRange($this, $key, $name, $column, $name_second);
1294
	}
1295
1296
1297
	/**
1298
	 * Check whether given key already exists in $this->filters
1299
	 * @param  string $key
1300
	 * @throws DataGridException
1301
	 */
1302
	protected function addFilterCheck($key)
1303
	{
1304 1
		if (isset($this->filters[$key])) {
1305
			throw new DataGridException("There is already action at key [$key] defined.");
1306
		}
1307 1
	}
1308
1309
1310
	/**
1311
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1312
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1313
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1314
	 */
1315
	public function assableFilters()
1316
	{
1317 1
		foreach ($this->filter as $key => $value) {
1318
			if (!isset($this->filters[$key])) {
1319
				$this->deleteSessionData($key);
1320
1321
				continue;
1322
			}
1323
1324
			if (is_array($value) || $value instanceof \Traversable) {
1325
				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...
1326
					$this->filters[$key]->setValue($value);
1327
				}
1328
			} else {
1329
				if ($value !== '' && $value !== NULL) {
1330
					$this->filters[$key]->setValue($value);
1331
				}
1332
			}
1333
		}
1334
1335 1
		foreach ($this->columns as $key => $column) {
1336
			if (isset($this->sort[$key])) {
1337
				$column->setSort($this->sort);
1338
			}
1339
		}
1340
1341
		/**
1342
		 * Invoke possible events
1343
		 */
1344 1
		$this->onFiltersAssabled($this->filters);
0 ignored issues
show
Documentation Bug introduced by
The method onFiltersAssabled 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...
1345
1346 1
		return $this->filters;
1347
	}
1348
1349
1350
	/**
1351
	 * Remove filter
1352
	 * @param string $key
1353
	 * @return void
1354
	 */
1355
	public function removeFilter($key)
1356
	{
1357
		unset($this->filters[$key]);
1358
	}
1359
1360
1361
	/**
1362
	 * Get defined filter
1363
	 * @param  string $key
1364
	 * @return Filter\Filter
1365
	 */
1366
	public function getFilter($key)
1367
	{
1368
		if (!isset($this->filters[$key])) {
1369
			throw new DataGridException("Filter [{$key}] is not defined");
1370
		}
1371
1372
		return $this->filters[$key];
1373
	}
1374
1375
1376
	/**
1377
	 * @param bool $strict
1378
	 */
1379
	public function setStrictSessionFilterValues($strict = TRUE)
1380
	{
1381
		$this->strict_session_filter_values = (bool) $strict;
1382
	}
1383
1384
1385
	/********************************************************************************
1386
	 *                                  FILTERING                                   *
1387
	 ********************************************************************************/
1388
1389
1390
	/**
1391
	 * Is filter active?
1392
	 * @return bool
1393
	 */
1394
	public function isFilterActive()
1395
	{
1396
		$is_filter = ArraysHelper::testTruthy($this->filter);
1397
1398
		return ($is_filter) || $this->force_filter_active;
1399
	}
1400
1401
1402
	/**
1403
	 * Tell that filter is active from whatever reasons
1404
	 * return static
1405
	 */
1406
	public function setFilterActive()
1407
	{
1408
		$this->force_filter_active = TRUE;
1409
1410
		return $this;
1411
	}
1412
1413
1414
	/**
1415
	 * Set filter values (force - overwrite user data)
1416
	 * @param array $filter
1417
	 * @return static
1418
	 */
1419
	public function setFilter(array $filter)
1420
	{
1421
		$this->filter = $filter;
1422
1423
		$this->saveSessionData('_grid_has_filtered', 1);
1424
1425
		return $this;
1426
	}
1427
1428
1429
	/**
1430
	 * If we want to sent some initial filter
1431
	 * @param array $filter
0 ignored issues
show
Documentation introduced by
There is no parameter named $filter. Did you maybe mean $default_filter?

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

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

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

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

Loading history...
1432
	 * @param bool  $use_on_reset
1433
	 * @return static
1434
	 */
1435
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1436
	{
1437
		foreach ($default_filter as $key => $value) {
1438
			$filter = $this->getFilter($key);
1439
1440
			if (!$filter) {
1441
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1442
			}
1443
1444
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1445
				throw new DataGridException(
1446
					"Default value of filter [$key] - MultiSelect has to be an array"
1447
				);
1448
			}
1449
1450
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1451
				if (!is_array($value)) {
1452
					throw new DataGridException(
1453
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1454
					);
1455
				}
1456
1457
				$temp = $value;
1458
				unset($temp['from'], $temp['to']);
1459
1460
				if (!empty($temp)) {
1461
					throw new DataGridException(
1462
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1463
					);
1464
				}
1465
			}
1466
		}
1467
1468
		$this->default_filter = $default_filter;
1469
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1470
1471
		return $this;
1472
	}
1473
1474
1475
	/**
1476
	 * User may set default filter, find it
1477
	 * @return void
1478
	 */
1479
	public function findDefaultFilter()
1480
	{
1481 1
		if (!empty($this->filter)) {
1482
			return;
1483
		}
1484
1485 1
		if ($this->getSessionData('_grid_has_filtered')) {
1486
			return;
1487
		}
1488
1489 1
		if (!empty($this->default_filter)) {
1490
			$this->filter = $this->default_filter;
1491
		}
1492
1493 1
		foreach ($this->filter as $key => $value) {
1494
			$this->saveSessionData($key, $value);
1495
		}
1496 1
	}
1497
1498
1499
	/**
1500
	 * FilterAndGroupAction form factory
1501
	 * @return Form
1502
	 */
1503
	public function createComponentFilter()
1504
	{
1505
		$form = new Form($this, 'filter');
1506
1507
		$form->setMethod(static::$form_method);
1508
1509
		$form->setTranslator($this->getTranslator());
1510
1511
		/**
1512
		 * InlineEdit part
1513
		 */
1514
		$inline_edit_container = $form->addContainer('inline_edit');
1515
1516 View Code Duplication
		if ($this->inlineEdit instanceof InlineEdit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1517
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1518
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1519
				->setValidationScope(FALSE);
1520
1521
			$this->inlineEdit->onControlAdd($inline_edit_container);
1522
			$this->inlineEdit->onControlAfterAdd($inline_edit_container);
1523
		}
1524
1525
		/**
1526
		 * InlineAdd part
1527
		 */
1528
		$inline_add_container = $form->addContainer('inline_add');
1529
1530 View Code Duplication
		if ($this->inlineAdd instanceof InlineEdit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1531
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1532
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1533
				->setValidationScope(FALSE)
1534
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1535
1536
			$this->inlineAdd->onControlAdd($inline_add_container);
1537
			$this->inlineAdd->onControlAfterAdd($inline_add_container);
1538
		}
1539
1540
		/**
1541
		 * ItemDetail form part
1542
		 */
1543
		$items_detail_form = $this->getItemDetailForm();
1544
1545
		if ($items_detail_form instanceof Nette\Forms\Container) {
1546
			$form['items_detail_form'] = $items_detail_form;
1547
		}
1548
1549
		/**
1550
		 * Filter part
1551
		 */
1552
		$filter_container = $form->addContainer('filter');
1553
1554
		foreach ($this->filters as $filter) {
1555
			$filter->addToFormContainer($filter_container);
1556
		}
1557
1558
		if (!$this->hasAutoSubmit()) {
1559
			$filter_container['submit'] = $this->getFilterSubmitButton();
1560
		}
1561
1562
		/**
1563
		 * Group action part
1564
		 */
1565
		$group_action_container = $form->addContainer('group_action');
1566
1567
		if ($this->hasGroupActions()) {
1568
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1569
		}
1570
1571
		if (!$form->isSubmitted()) {
1572
			$this->setFilterContainerDefaults($form['filter'], $this->filter);
1573
		}
1574
1575
		/**
1576
		 * Per page part
1577
		 */
1578
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1579
			->setTranslator(NULL);
1580
1581
		if (!$form->isSubmitted()) {
1582
			$form['per_page']->setValue($this->getPerPage());
1583
		}
1584
1585
		$form->addSubmit('per_page_submit', 'ublaboo_datagrid.per_page_submit');
1586
		
1587
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1588
	}
1589
1590
1591
	/**
1592
	 * @param  Nette\Forms\Container  $container
1593
	 * @param  array|\Iterator  $values
1594
	 * @return void
1595
	 */
1596
	public function setFilterContainerDefaults(Nette\Forms\Container $container, $values)
1597
	{
1598
		foreach ($container->getComponents() as $key => $control) {
1599
			if (!isset($values[$key])) {
1600
				continue;
1601
			}
1602
1603
			if ($control instanceof Nette\Forms\Container) {
1604
				$this->setFilterContainerDefaults($control, $values[$key]);
1605
1606
				continue;
1607
			}
1608
1609
			$value = $values[$key];
1610
1611
			if ($value instanceof \DateTime && ($filter = $this->getFilter($key)) instanceof IFilterDate) {
1612
				$value = $value->format($filter->getPhpFormat());
1613
			}
1614
1615
			try {
1616
				$control->setValue($value);
1617
1618
			} catch (Nette\InvalidArgumentException $e) {
1619
				if ($this->strict_session_filter_values) {
1620
					throw $e;
1621
				}
1622
			}
1623
		}
1624
	}
1625
1626
1627
	/**
1628
	 * Set $this->filter values after filter form submitted
1629
	 * @param  Form $form
1630
	 * @return void
1631
	 */
1632
	public function filterSucceeded(Form $form)
1633
	{
1634
		if ($this->snippets_set) {
1635
			return;
1636
		}
1637
1638
		$values = $form->getValues();
1639
1640
		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...
1641
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1642
				return;
1643
			}
1644
		}
1645
1646
		/**
1647
		 * Per page
1648
		 */
1649
		$this->saveSessionData('_grid_per_page', $values->per_page);
1650
		$this->per_page = $values->per_page;
1651
1652
		/**
1653
		 * Inline edit
1654
		 */
1655
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1656
			$edit = $form['inline_edit'];
1657
1658
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1659
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1660
				$primary_where_column = $form->getHttpData(
1661
					Form::DATA_LINE,
1662
					'inline_edit[_primary_where_column]'
1663
				);
1664
1665
				if ($edit['submit']->isSubmittedBy()) {
1666
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1667
					$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...
1668
					$this->getPresenter()->payload->_datagrid_name = $this->getName();
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...
1669
				} else {
1670
					$this->getPresenter()->payload->_datagrid_inline_edit_cancel = $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...
1671
					$this->getPresenter()->payload->_datagrid_name = $this->getName();
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...
1672
				}
1673
1674
				if ($edit['submit']->isSubmittedBy() && !empty($this->inlineEdit->onCustomRedraw)) {
1675
					$this->inlineEdit->onCustomRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onCustomRedraw does not exist on object<Ublaboo\DataGrid\InlineEdit\InlineEdit>? 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...
1676
				} else {
1677
					$this->redrawItem($id, $primary_where_column);
1678
				}
1679
1680
				return;
1681
			}
1682
		}
1683
1684
		/**
1685
		 * Inline add
1686
		 */
1687
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1688
			$add = $form['inline_add'];
1689
1690
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1691
				if ($add['submit']->isSubmittedBy()) {
1692
					$this->inlineAdd->onSubmit($values->inline_add);
0 ignored issues
show
Bug introduced by
The call to onSubmit() misses a required argument $values.

This check looks for function calls that miss required arguments.

Loading history...
1693
1694
					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...
1695
						$this->getPresenter()->payload->_datagrid_inline_added = TRUE;
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...
1696
					}
1697
				}
1698
1699
				return;
1700
			}
1701
		}
1702
1703
		/**
1704
		 * Filter itself
1705
		 */
1706
		$values = $values['filter'];
1707
1708
		foreach ($values as $key => $value) {
1709
			/**
1710
			 * Session stuff
1711
			 */
1712
			$this->saveSessionData($key, $value);
1713
1714
			/**
1715
			 * Other stuff
1716
			 */
1717
			$this->filter[$key] = $value;
1718
		}
1719
1720
		if (!empty($values)) {
1721
			$this->saveSessionData('_grid_has_filtered', 1);
1722
		}
1723
1724
		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...
1725
			$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...
1726
1727
			foreach ($this->columns as $key => $column) {
1728
				if ($column->isSortable()) {
1729
					$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...
1730
						'sort' => $column->getSortNext()
1731
					]);
1732
				}
1733
			}
1734
		}
1735
1736
		$this->reload();
1737
	}
1738
1739
1740
	/**
1741
	 * Should be datagrid filters rendered separately?
1742
	 * @param bool $out
1743
	 * @return static
1744
	 */
1745
	public function setOuterFilterRendering($out = TRUE)
1746
	{
1747
		$this->outer_filter_rendering = (bool) $out;
1748
1749
		return $this;
1750
	}
1751
1752
1753
	/**
1754
	 * Are datagrid filters rendered separately?
1755
	 * @return bool
1756
	 */
1757
	public function hasOuterFilterRendering()
1758
	{
1759
		return $this->outer_filter_rendering;
1760
	}
1761
1762
1763
	/**
1764
	 * @param bool $collapsible_outer_filters
1765
	 */
1766
	public function setCollapsibleOuterFilters($collapsible_outer_filters = TRUE)
1767
	{
1768
		$this->collapsible_outer_filters = (bool) $collapsible_outer_filters;
1769
	}
1770
1771
1772
	/**
1773
	 * @return bool
1774
	 */
1775
	public function hasCollapsibleOuterFilters()
1776
	{
1777
		return $this->collapsible_outer_filters;
1778
	}
1779
1780
1781
	/**
1782
	 * Try to restore session stuff
1783
	 * @return void
1784
	 * @throws DataGridFilterNotFoundException
1785
	 */
1786
	public function findSessionValues()
1787
	{
1788 1
		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...
1789
			return;
1790
		}
1791
1792 1
		if (!$this->remember_state) {
1793
			return;
1794
		}
1795
1796 1
		if ($page = $this->getSessionData('_grid_page')) {
1797
			$this->page = $page;
1798
		}
1799
1800 1
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1801
			$this->per_page = $per_page;
1802
		}
1803
1804 1
		if ($sort = $this->getSessionData('_grid_sort')) {
1805
			$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...
1806
		}
1807
1808 1
		foreach ($this->getSessionData() as $key => $value) {
1809
			$other_session_keys = [
1810 1
				'_grid_per_page',
1811
				'_grid_sort',
1812
				'_grid_page',
1813
				'_grid_has_sorted',
1814
				'_grid_has_filtered',
1815
				'_grid_hidden_columns',
1816
				'_grid_hidden_columns_manipulated'
1817
			];
1818
1819 1
			if (!in_array($key, $other_session_keys)) {
1820
				try {
1821
					$this->getFilter($key);
1822
1823
					$this->filter[$key] = $value;
1824
1825
				} catch (DataGridException $e) {
1826
					if ($this->strict_session_filter_values) {
1827 1
						throw new DataGridFilterNotFoundException("Session filter: Filter [$key] not found");
1828
					}
1829
				}
1830
			}
1831
		}
1832
1833
		/**
1834
		 * When column is sorted via custom callback, apply it
1835
		 */
1836 1
		if (empty($this->sort_callback) && !empty($this->sort)) {
1837
			foreach ($this->sort as $key => $order) {
1838
				try {
1839
					$column = $this->getColumn($key);
1840
1841
				} catch (DataGridColumnNotFoundException $e) {
1842
					$this->deleteSessionData('_grid_sort');
1843
					$this->sort = [];
1844
1845
					return;
1846
				}
1847
1848
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1849
					$this->sort_callback = $column->getSortableCallback();
1850
				}
1851
			}
1852
		}
1853 1
	}
1854
1855
1856
	/********************************************************************************
1857
	 *                                    EXPORTS                                   *
1858
	 ********************************************************************************/
1859
1860
1861
	/**
1862
	 * Add export of type callback
1863
	 * @param string $text
1864
	 * @param callable $callback
1865
	 * @param bool $filtered
1866
	 * @return Export\Export
1867
	 */
1868
	public function addExportCallback($text, $callback, $filtered = FALSE)
1869
	{
1870 1
		if (!is_callable($callback)) {
1871
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1872
		}
1873
1874 1
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1875
	}
1876
1877
1878
	/**
1879
	 * Add already implemented csv export
1880
	 * @param string $text
1881
	 * @param string $csv_file_name
1882
	 * @param string|null $output_encoding
1883
	 * @param string|null $delimiter
1884
	 * @param bool $include_bom
1885
	 * @return Export\Export
1886
	 */
1887 View Code Duplication
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
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...
1888
	{
1889 1
		return $this->addToExports(new Export\ExportCsv(
1890
			$this,
1891
			$text,
1892
			$csv_file_name,
1893 1
			FALSE,
1894
			$output_encoding,
1895
			$delimiter,
1896
			$include_bom
1897
		));
1898
	}
1899
1900
1901
	/**
1902
	 * Add already implemented csv export, but for filtered data
1903
	 * @param string $text
1904
	 * @param string $csv_file_name
1905
	 * @param string|null $output_encoding
1906
	 * @param string|null $delimiter
1907
	 * @param bool $include_bom
1908
	 * @return Export\Export
1909
	 */
1910 View Code Duplication
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
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...
1911
	{
1912
		return $this->addToExports(new Export\ExportCsv(
1913
			$this,
1914
			$text,
1915
			$csv_file_name,
1916
			TRUE,
1917
			$output_encoding,
1918
			$delimiter,
1919
			$include_bom
1920
		));
1921
	}
1922
1923
1924
	/**
1925
	 * Add export to array
1926
	 * @param Export\Export $export
1927
	 * @return Export\Export
1928
	 */
1929
	protected function addToExports(Export\Export $export)
1930
	{
1931 1
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1932
1933 1
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1934
1935 1
		return $this->exports[$id] = $export;
1936
	}
1937
1938
1939
	public function resetExportsLinks()
1940
	{
1941
		foreach ($this->exports as $id => $export) {
1942
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1943
		}
1944
	}
1945
1946
1947
	/********************************************************************************
1948
	 *                                TOOLBAR BUTTONS                               *
1949
	 ********************************************************************************/
1950
1951
1952
	/**
1953
	 * Add toolbar button
1954
	 * @param string $href
1955
	 * @param string $text
1956
	 * @param array  $params
1957
	 * @return ToolbarButton
1958
	 */
1959
	public function addToolbarButton($href, $text = '', $params = [])
1960
	{
1961
		$button = new ToolbarButton($this, $href, $text, $params);
1962
1963
		return $this->toolbar_buttons[] = $button;
1964
	}
1965
1966
1967
	/********************************************************************************
1968
	 *                                 GROUP ACTIONS                                *
1969
	 ********************************************************************************/
1970
1971
1972
	/**
1973
	 * Alias for add group select action
1974
	 * @param string $title
1975
	 * @param array  $options
1976
	 * @return GroupAction\GroupAction
1977
	 */
1978
	public function addGroupAction($title, $options = [])
1979
	{
1980
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1981
	}
1982
1983
1984
	/**
1985
	 * Add group action (select box)
1986
	 * @param string $title
1987
	 * @param array  $options
1988
	 * @return GroupAction\GroupAction
1989
	 */
1990
	public function addGroupSelectAction($title, $options = [])
1991
	{
1992
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1993
	}
1994
1995
1996
	/**
1997
	 * Add group action (multiselect box)
1998
	 * @param string $title
1999
	 * @param array  $options
2000
	 * @return GroupAction\GroupAction
2001
	 */
2002
	public function addGroupMultiSelectAction($title, $options = [])
2003
	{
2004
		return $this->getGroupActionCollection()->addGroupMultiSelectAction($title, $options);
2005
	}
2006
2007
2008
	/**
2009
	 * Add group action (text input)
2010
	 * @param string $title
2011
	 * @return GroupAction\GroupAction
2012
	 */
2013
	public function addGroupTextAction($title)
2014
	{
2015
		return $this->getGroupActionCollection()->addGroupTextAction($title);
2016
	}
2017
2018
2019
	/**
2020
	 * Add group action (textarea)
2021
	 * @param string $title
2022
	 * @return GroupAction\GroupAction
2023
	 */
2024
	public function addGroupTextareaAction($title)
2025
	{
2026
		return $this->getGroupActionCollection()->addGroupTextareaAction($title);
2027
	}
2028
2029
2030
	/**
2031
	 * Get collection of all group actions
2032
	 * @return GroupAction\GroupActionCollection
2033
	 */
2034
	public function getGroupActionCollection()
2035
	{
2036
		if (!$this->group_action_collection) {
2037
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
2038
		}
2039
2040
		return $this->group_action_collection;
2041
	}
2042
2043
2044
	/**
2045
	 * Has datagrid some group actions?
2046
	 * @return bool
2047
	 */
2048
	public function hasGroupActions()
2049
	{
2050
		return (bool) $this->group_action_collection;
2051
	}
2052
2053
2054
	/********************************************************************************
2055
	 *                                   HANDLERS                                   *
2056
	 ********************************************************************************/
2057
2058
2059
	/**
2060
	 * Handler for changind page (just refresh site with page as persistent paramter set)
2061
	 * @param  int  $page
2062
	 * @return void
2063
	 */
2064
	public function handlePage($page)
2065
	{
2066
		/**
2067
		 * Session stuff
2068
		 */
2069
		$this->page = $page;
2070
		$this->saveSessionData('_grid_page', $page);
2071
2072
		$this->reload(['table']);
2073
	}
2074
2075
2076
	/**
2077
	 * Handler for sorting
2078
	 * @param array $sort
2079
	 * @return void
2080
	 * @throws DataGridColumnNotFoundException
2081
	 */
2082
	public function handleSort(array $sort)
2083
	{
2084
		foreach ($sort as $key => $value) {
2085
			try {
2086
				$column = $this->getColumn($key);
2087
2088
			} catch (DataGridColumnNotFoundException $e) {
2089
				unset($sort[$key]);
2090
				continue;
2091
			}
2092
2093
			if ($column->sortableResetPagination()) {
2094
				$this->saveSessionData('_grid_page', $this->page = 1);
2095
			}
2096
2097
			if ($column->getSortableCallback()) {
2098
				$this->sort_callback = $column->getSortableCallback();
2099
			}
2100
		}
2101
2102
		$this->saveSessionData('_grid_has_sorted', 1);
2103
		$this->saveSessionData('_grid_sort', $this->sort = $sort);
2104
		$this->reload(['table']);
2105
	}
2106
2107
2108
	/**
2109
	 * Handler for reseting the filter
2110
	 * @return void
2111
	 */
2112
	public function handleResetFilter()
2113
	{
2114
		/**
2115
		 * Session stuff
2116
		 */
2117
		$this->deleteSessionData('_grid_page');
2118
2119
		if ($this->default_filter_use_on_reset) {
2120
			$this->deleteSessionData('_grid_has_filtered');
2121
		}
2122
2123
		if ($this->default_sort_use_on_reset) {
2124
			$this->deleteSessionData('_grid_has_sorted');
2125
		}
2126
2127
		foreach ($this->getSessionData() as $key => $value) {
2128
			if (!in_array($key, [
2129
				'_grid_per_page',
2130
				'_grid_sort',
2131
				'_grid_page',
2132
				'_grid_has_filtered',
2133
				'_grid_has_sorted',
2134
				'_grid_hidden_columns',
2135
				'_grid_hidden_columns_manipulated'
2136
				])) {
2137
2138
				$this->deleteSessionData($key);
2139
			}
2140
		}
2141
2142
		$this->filter = [];
2143
2144
		$this->reload(['grid']);
2145
	}
2146
2147
2148
	/**
2149
	 * @param  string $key
2150
	 * @return void
2151
	 */
2152
	public function handleResetColumnFilter($key)
2153
	{
2154
		$this->deleteSessionData($key);
2155
		unset($this->filter[$key]);
2156
2157
		$this->reload(['grid']);
2158
	}
2159
2160
2161
	/**
2162
	 * @param bool $reset
2163
	 * @return static
2164
	 */
2165
	public function setColumnReset($reset = TRUE)
2166
	{
2167
		$this->has_column_reset = (bool) $reset;
2168
2169
		return $this;
2170
	}
2171
2172
2173
	/**
2174
	 * @return bool
2175
	 */
2176
	public function hasColumnReset()
2177
	{
2178 1
		return $this->has_column_reset;
2179
	}
2180
2181
2182
	/**
2183
	 * @param  Filter\Filter[] $filters
2184
	 * @return void
2185
	 */
2186
	public function sendNonEmptyFiltersInPayload($filters)
2187
	{
2188 1
		if (!$this->hasColumnReset()) {
2189
			return;
2190
		}
2191
2192 1
		$non_empty_filters = [];
2193
2194 1
		foreach ($filters as $filter) {
2195 1
			if ($filter->isValueSet()) {
2196 1
				$non_empty_filters[] = $filter->getKey();
2197
			}
2198
		}
2199
2200 1
		$this->getPresenter()->payload->non_empty_filters = $non_empty_filters;
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...
2201 1
	}
2202
2203
2204
	/**
2205
	 * Handler for export
2206
	 * @param  int $id Key for particular export class in array $this->exports
2207
	 * @return void
2208
	 */
2209
	public function handleExport($id)
2210
	{
2211 1
		if (!isset($this->exports[$id])) {
2212
			throw new Nette\Application\ForbiddenRequestException;
2213
		}
2214
2215 1
		if (!empty($this->columns_export_order)) {
2216
			$this->setColumnsOrder($this->columns_export_order);
2217
		}
2218
2219 1
		$export = $this->exports[$id];
2220
2221
		/**
2222
		 * Invoke possible events
2223
		 */
2224 1
		$this->onExport($this);
0 ignored issues
show
Documentation Bug introduced by
The method onExport 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...
2225
2226 1
		if ($export->isFiltered()) {
2227 1
			$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...
2228 1
			$filter    = $this->assableFilters();
2229
		} else {
2230 1
			$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...
2231 1
			$filter    = [];
2232
		}
2233
2234 1
		if (NULL === $this->dataModel) {
2235 1
			throw new DataGridException('You have to set a data source first.');
2236
		}
2237
2238 1
		$rows = [];
2239
2240 1
		$items = Nette\Utils\Callback::invokeArgs(
2241 1
			[$this->dataModel, 'filterData'], [
2242 1
				NULL,
2243 1
				$this->createSorting($this->sort, $this->sort_callback),
2244 1
				$filter
2245
			]
2246
		);
2247
2248 1
		foreach ($items as $item) {
2249 1
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
2250
		}
2251
2252 1
		if ($export instanceof Export\ExportCsv) {
2253 1
			$export->invoke($rows);
2254
		} else {
2255 1
			$export->invoke($items);
2256
		}
2257
2258 1
		if ($export->isAjax()) {
2259
			$this->reload();
2260
		}
2261 1
	}
2262
2263
2264
	/**
2265
	 * Handler for getting children of parent item (e.g. category)
2266
	 * @param  int $parent
2267
	 * @return void
2268
	 */
2269
	public function handleGetChildren($parent)
2270
	{
2271
		$this->setDataSource(
2272
			call_user_func($this->tree_view_children_callback, $parent)
2273
		);
2274
2275
		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...
2276
			$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...
2277
			$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...
2278
2279
			$this->redrawControl('items');
2280
2281
			$this->onRedraw();
2282
		} else {
2283
			$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\Component, Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, 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...
2284
		}
2285
	}
2286
2287
2288
	/**
2289
	 * Handler for getting item detail
2290
	 * @param  mixed $id
2291
	 * @return void
2292
	 */
2293
	public function handleGetItemDetail($id)
2294
	{
2295
		$this->getTemplate()->add('toggle_detail', $id);
2296
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
2297
2298
		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...
2299
			$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...
2300
			$this->redrawControl('items');
2301
2302
			/**
2303
			 * Only for nette 2.4
2304
			 */
2305
			if (method_exists($this->getTemplate()->getLatte(), 'addProvider')) {
2306
				$this->redrawControl('gridSnippets');
2307
			}
2308
2309
			$this->onRedraw();
2310
		} else {
2311
			$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\Component, Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, 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...
2312
		}
2313
	}
2314
2315
2316
	/**
2317
	 * Handler for inline editing
2318
	 * @param  mixed $id
2319
	 * @param  mixed $key
2320
	 * @return void
2321
	 */
2322
	public function handleEdit($id, $key)
2323
	{
2324
		$column = $this->getColumn($key);
2325
		$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...
2326
2327
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
2328
	}
2329
2330
2331
	/**
2332
	 * Redraw $this
2333
	 * @return void
2334
	 */
2335
	public function reload($snippets = [])
2336
	{
2337
		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...
2338
			$this->redrawControl('tbody');
2339
			$this->redrawControl('pagination');
2340
2341
			/**
2342
			 * manualy reset exports links...
2343
			 */
2344
			$this->resetExportsLinks();
2345
			$this->redrawControl('exports');
2346
2347
			foreach ($snippets as $snippet) {
2348
				$this->redrawControl($snippet);
2349
			}
2350
2351
			$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...
2352
			$this->getPresenter()->payload->_datagrid_name = $this->getName();
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...
2353
2354
			$this->onRedraw();
2355
		} else {
2356
			$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\Component, Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, 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...
2357
		}
2358
	}
2359
2360
2361
	/**
2362
	 * Handler for column status
2363
	 * @param  string $id
2364
	 * @param  string $key
2365
	 * @param  string $value
2366
	 * @return void
2367
	 */
2368
	public function handleChangeStatus($id, $key, $value)
2369
	{
2370
		if (empty($this->columns[$key])) {
2371
			throw new DataGridException("ColumnStatus[$key] does not exist");
2372
		}
2373
2374
		$this->columns[$key]->onChange($id, $value);
2375
	}
2376
2377
2378
	/**
2379
	 * Redraw just one row via ajax
2380
	 * @param  int   $id
2381
	 * @param  mixed $primary_where_column
2382
	 * @return void
2383
	 */
2384
	public function redrawItem($id, $primary_where_column = NULL)
2385
	{
2386
		$this->snippets_set = TRUE;
2387
2388
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
2389
2390
		$this->redrawControl('items');
2391
2392
		$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...
2393
2394
		$this->onRedraw();
2395
	}
2396
2397
2398
	/**
2399
	 * Tell datagrid to display all columns
2400
	 * @return void
2401
	 */
2402
	public function handleShowAllColumns()
2403
	{
2404
		$this->deleteSessionData('_grid_hidden_columns');
2405
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2406
2407
		$this->redrawControl();
2408
2409
		$this->onRedraw();
2410
	}
2411
2412
2413
	/**
2414
	 * Tell datagrid to display default columns
2415
	 * @return void
2416
	 */
2417
	public function handleShowDefaultColumns()
2418
	{
2419
		$this->deleteSessionData('_grid_hidden_columns');
2420
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
2421
2422
		$this->redrawControl();
2423
2424
		$this->onRedraw();
2425
	}
2426
2427
2428
	/**
2429
	 * Reveal particular column
2430
	 * @param  string $column
2431
	 * @return void
2432
	 */
2433 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...
2434
	{
2435
		$columns = $this->getSessionData('_grid_hidden_columns');
2436
2437
		if (!empty($columns)) {
2438
			$pos = array_search($column, $columns);
2439
2440
			if ($pos !== FALSE) {
2441
				unset($columns[$pos]);
2442
			}
2443
		}
2444
2445
		$this->saveSessionData('_grid_hidden_columns', $columns);
2446
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2447
2448
		$this->redrawControl();
2449
2450
		$this->onRedraw();
2451
	}
2452
2453
2454
	/**
2455
	 * Notice datagrid to not display particular columns
2456
	 * @param  string $column
2457
	 * @return void
2458
	 */
2459 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...
2460
	{
2461
		/**
2462
		 * Store info about hiding a column to session
2463
		 */
2464
		$columns = $this->getSessionData('_grid_hidden_columns');
2465
2466
		if (empty($columns)) {
2467
			$columns = [$column];
2468
		} else if (!in_array($column, $columns)) {
2469
			array_push($columns, $column);
2470
		}
2471
2472
		$this->saveSessionData('_grid_hidden_columns', $columns);
2473
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2474
2475
		$this->redrawControl();
2476
2477
		$this->onRedraw();
2478
	}
2479
2480
2481
	public function handleActionCallback($__key, $__id)
2482
	{
2483
		$action = $this->getAction($__key);
2484
2485
		if (!($action instanceof Column\ActionCallback)) {
2486
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2487
		}
2488
2489
		$action->onClick($__id);
2490
	}
2491
2492
2493
	/********************************************************************************
2494
	 *                                  PAGINATION                                  *
2495
	 ********************************************************************************/
2496
2497
2498
	/**
2499
	 * Set options of select "items_per_page"
2500
	 * @param array $items_per_page_list
2501
	 * @return static
2502
	 */
2503
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
2504
	{
2505
		$this->items_per_page_list = $items_per_page_list;
2506
2507
		if ($include_all) {
2508
			$this->items_per_page_list[] = 'all';
2509
		}
2510
2511
		return $this;
2512
	}
2513
2514
2515
	/**
2516
	 * Set default "items per page" value in pagination select
2517
	 * @param $count
2518
	 * @return static
2519
	 */
2520
	public function setDefaultPerPage($count)
2521
	{
2522
		$this->default_per_page = $count;
2523
2524
		return $this;
2525
	}
2526
2527
2528
	/**
2529
	 * User may set default "items per page" value, apply it
2530
	 * @return void
2531
	 */
2532
	public function findDefaultPerPage()
2533
	{
2534
		if (!empty($this->per_page)) {
2535
			return;
2536
		}
2537
2538
		if (!empty($this->default_per_page)) {
2539
			$this->per_page = $this->default_per_page;
2540
		}
2541
2542
		$this->saveSessionData('_grid_per_page', $this->per_page);
2543
	}
2544
2545
2546
	/**
2547
	 * Paginator factory
2548
	 * @return Components\DataGridPaginator\DataGridPaginator
2549
	 */
2550
	public function createComponentPaginator()
2551
	{
2552
		/**
2553
		 * Init paginator
2554
		 */
2555
		$component = new Components\DataGridPaginator\DataGridPaginator(
2556
			$this->getTranslator(),
2557
			static::$icon_prefix
2558
		);
2559
		$paginator = $component->getPaginator();
2560
2561
		$paginator->setPage($this->page);
2562
		$paginator->setItemsPerPage($this->getPerPage());
2563
2564
		return $component;
2565
	}
2566
2567
2568
	/**
2569
	 * Get parameter per_page
2570
	 * @return int
2571
	 */
2572
	public function getPerPage()
2573
	{
2574
		$items_per_page_list = $this->getItemsPerPageList();
2575
2576
		$per_page = $this->per_page ?: reset($items_per_page_list);
2577
2578
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2579
			$per_page = reset($items_per_page_list);
2580
		}
2581
2582
		return $per_page;
2583
	}
2584
2585
2586
	/**
2587
	 * Get associative array of items_per_page_list
2588
	 * @return array
2589
	 */
2590
	public function getItemsPerPageList()
2591
	{
2592
		if (empty($this->items_per_page_list)) {
2593
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2594
		}
2595
2596
		$list = array_flip($this->items_per_page_list);
2597
2598
		foreach ($list as $key => $value) {
2599
			$list[$key] = $key;
2600
		}
2601
2602
		if (array_key_exists('all', $list)) {
2603
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2604
		}
2605
2606
		return $list;
2607
	}
2608
2609
2610
	/**
2611
	 * Order Grid to "be paginated"
2612
	 * @param bool $do
2613
	 * @return static
2614
	 */
2615
	public function setPagination($do)
2616
	{
2617
		$this->do_paginate = (bool) $do;
2618
2619
		return $this;
2620
	}
2621
2622
2623
	/**
2624
	 * Tell whether Grid is paginated
2625
	 * @return bool
2626
	 */
2627
	public function isPaginated()
2628
	{
2629
		return $this->do_paginate;
2630
	}
2631
2632
2633
	/**
2634
	 * Return current paginator class
2635
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2636
	 */
2637
	public function getPaginator()
2638
	{
2639
		if ($this->isPaginated() && $this->getPerPage() !== 'all') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $this->getPerPage() (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
2640
			return $this['paginator'];
2641
		}
2642
2643
		return NULL;
2644
	}
2645
2646
2647
	/********************************************************************************
2648
	 *                                     I18N                                     *
2649
	 ********************************************************************************/
2650
2651
2652
	/**
2653
	 * Set datagrid translator
2654
	 * @param Nette\Localization\ITranslator $translator
2655
	 * @return static
2656
	 */
2657
	public function setTranslator(Nette\Localization\ITranslator $translator)
2658
	{
2659
		$this->translator = $translator;
2660
2661
		return $this;
2662
	}
2663
2664
2665
	/**
2666
	 * Get translator for datagrid
2667
	 * @return Nette\Localization\ITranslator
2668
	 */
2669
	public function getTranslator()
2670
	{
2671 1
		if (!$this->translator) {
2672 1
			$this->translator = new Localization\SimpleTranslator;
2673
		}
2674
2675 1
		return $this->translator;
2676
	}
2677
2678
2679
	/********************************************************************************
2680
	 *                                 COLUMNS ORDER                                *
2681
	 ********************************************************************************/
2682
2683
2684
	/**
2685
	 * Set order of datagrid columns
2686
	 * @param array $order
2687
	 * @return static
2688
	 */
2689
	public function setColumnsOrder($order)
2690
	{
2691
		$new_order = [];
2692
2693
		foreach ($order as $key) {
2694
			if (isset($this->columns[$key])) {
2695
				$new_order[$key] = $this->columns[$key];
2696
			}
2697
		}
2698
2699
		if (sizeof($new_order) === sizeof($this->columns)) {
2700
			$this->columns = $new_order;
2701
		} else {
2702
			throw new DataGridException('When changing columns order, you have to specify all columns');
2703
		}
2704
2705
		return $this;
2706
	}
2707
2708
2709
	/**
2710
	 * Columns order may be different for export and normal grid
2711
	 * @param array $order
2712
	 */
2713
	public function setColumnsExportOrder($order)
2714
	{
2715
		$this->columns_export_order = (array) $order;
2716
	}
2717
2718
2719
	/********************************************************************************
2720
	 *                                SESSION & URL                                 *
2721
	 ********************************************************************************/
2722
2723
2724
	/**
2725
	 * Find some unique session key name
2726
	 * @return string
2727
	 */
2728
	public function getSessionSectionName()
2729
	{
2730 1
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2731
	}
2732
2733
2734
	/**
2735
	 * Should datagrid remember its filters/pagination/etc using session?
2736
	 * @param bool $remember
2737
	 * @return static
2738
	 */
2739
	public function setRememberState($remember = TRUE)
2740
	{
2741
		$this->remember_state = (bool) $remember;
2742
2743
		return $this;
2744
	}
2745
2746
2747
	/**
2748
	 * Should datagrid refresh url using history API?
2749
	 * @param bool $refresh
2750
	 * @return static
2751
	 */
2752
	public function setRefreshUrl($refresh = TRUE)
2753
	{
2754
		$this->refresh_url = (bool) $refresh;
2755
2756
2757
		return $this;
2758
	}
2759
2760
2761
	/**
2762
	 * Get session data if functionality is enabled
2763
	 * @param  string $key
2764
	 * @return mixed
2765
	 */
2766
	public function getSessionData($key = NULL, $default_value = NULL)
2767
	{
2768 1
		if (!$this->remember_state) {
2769
			return $key ? $default_value : [];
2770
		}
2771
2772 1
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2773
	}
2774
2775
2776
	/**
2777
	 * Save session data - just if it is enabled
2778
	 * @param  string $key
2779
	 * @param  mixed  $value
2780
	 * @return void
2781
	 */
2782
	public function saveSessionData($key, $value)
2783
	{
2784 1
		if ($this->remember_state) {
2785 1
			$this->grid_session->{$key} = $value;
2786
		}
2787 1
	}
2788
2789
2790
	/**
2791
	 * Delete session data
2792
	 * @return void
2793
	 */
2794
	public function deleteSessionData($key)
2795
	{
2796
		unset($this->grid_session->{$key});
2797
	}
2798
2799
2800
	/**
2801
	 * Delete session data
2802
	 * @return void
2803
	 * @deprecated
2804
	 */
2805
	public function deleteSesssionData($key)
2806
	{
2807
		@trigger_error('deleteSesssionData is deprecated, use deleteSessionData instead', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2808
		return $this->deleteSessionData($key);
2809
	}
2810
2811
2812
	/********************************************************************************
2813
	 *                                  ITEM DETAIL                                 *
2814
	 ********************************************************************************/
2815
2816
2817
	/**
2818
	 * Get items detail parameters
2819
	 * @return array
2820
	 */
2821
	public function getItemsDetail()
2822
	{
2823
		return $this->items_detail;
2824
	}
2825
2826
2827
	/**
2828
	 * Items can have thair detail - toggled
2829
	 * @param mixed $detail callable|string|bool
2830
	 * @param bool|NULL $primary_where_column
2831
	 * @return Column\ItemDetail
2832
	 */
2833
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2834
	{
2835
		if ($this->isSortable()) {
2836
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2837
		}
2838
2839
		$this->items_detail = new Column\ItemDetail(
2840
			$this,
2841
			$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...
2842
		);
2843
2844
		if (is_string($detail)) {
2845
			/**
2846
			 * Item detail will be in separate template
2847
			 */
2848
			$this->items_detail->setType('template');
2849
			$this->items_detail->setTemplate($detail);
2850
2851
		} else if (is_callable($detail)) {
2852
			/**
2853
			 * Item detail will be rendered via custom callback renderer
2854
			 */
2855
			$this->items_detail->setType('renderer');
2856
			$this->items_detail->setRenderer($detail);
2857
2858
		} else if (TRUE === $detail) {
2859
			/**
2860
			 * Item detail will be rendered probably via block #detail
2861
			 */
2862
			$this->items_detail->setType('block');
2863
2864
		} else {
2865
			throw new DataGridException(
2866
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2867
			);
2868
		}
2869
2870
		return $this->items_detail;
2871
	}
2872
2873
2874
	/**
2875
	 * @param callable $callable_set_container 
2876
	 * @return static
2877
	 */
2878
	public function setItemsDetailForm(callable $callable_set_container)
2879
	{
2880
		if ($this->items_detail instanceof Column\ItemDetail) {
2881
			$this->items_detail->setForm(
2882
				new Utils\ItemDetailForm($callable_set_container)
2883
			);
2884
2885
			return $this;
2886
		}
2887
2888
		throw new DataGridException('Please set the ItemDetail first.');
2889
	}
2890
2891
2892
	/**
2893
	 * @return Nette\Forms\Container|NULL
2894
	 */
2895
	public function getItemDetailForm()
2896
	{
2897
		if ($this->items_detail instanceof Column\ItemDetail) {
2898
			return $this->items_detail->getForm();
2899
		}
2900
2901
		return NULL;
2902
	}
2903
2904
2905
	/********************************************************************************
2906
	 *                                ROW PRIVILEGES                                *
2907
	 ********************************************************************************/
2908
2909
2910
	/**
2911
	 * @param  callable $condition
2912
	 * @return void
2913
	 */
2914
	public function allowRowsGroupAction(callable $condition)
2915
	{
2916
		$this->row_conditions['group_action'] = $condition;
2917
	}
2918
2919
2920
	/**
2921
	 * @param  callable $condition
2922
	 * @return void
2923
	 */
2924
	public function allowRowsInlineEdit(callable $condition)
2925
	{
2926
		$this->row_conditions['inline_edit'] = $condition;
2927
	}
2928
2929
2930
	/**
2931
	 * @param  string   $key
2932
	 * @param  callable $condition
2933
	 * @return void
2934
	 */
2935
	public function allowRowsAction($key, callable $condition)
2936
	{
2937
		$this->row_conditions['action'][$key] = $condition;
2938
	}
2939
2940
2941
	/**
2942
	 * @param  string      $name
2943
	 * @param  string|null $key
2944
	 * @return bool|callable
2945
	 */
2946
	public function getRowCondition($name, $key = NULL)
2947
	{
2948
		if (!isset($this->row_conditions[$name])) {
2949
			return FALSE;
2950
		}
2951
2952
		$condition = $this->row_conditions[$name];
2953
2954
		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...
2955
			return $condition;
2956
		}
2957
2958
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2959
	}
2960
2961
2962
	/********************************************************************************
2963
	 *                               COLUMN CALLBACK                                *
2964
	 ********************************************************************************/
2965
2966
2967
	/**
2968
	 * @param  string   $key
2969
	 * @param  callable $callback
2970
	 * @return void
2971
	 */
2972
	public function addColumnCallback($key, callable $callback)
2973
	{
2974
		$this->column_callbacks[$key] = $callback;
2975
	}
2976
2977
2978
	/**
2979
	 * @param  string $key
2980
	 * @return callable|null
2981
	 */
2982
	public function getColumnCallback($key)
2983
	{
2984
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2985
	}
2986
2987
2988
	/********************************************************************************
2989
	 *                                 INLINE EDIT                                  *
2990
	 ********************************************************************************/
2991
2992
2993
	/**
2994
	 * @return InlineEdit
2995
	 */
2996
	public function addInlineEdit($primary_where_column = NULL)
2997
	{
2998
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2999
3000
		return $this->inlineEdit;
3001
	}
3002
3003
3004
	/**
3005
	 * @return InlineEdit|null
3006
	 */
3007
	public function getInlineEdit()
3008
	{
3009
		return $this->inlineEdit;
3010
	}
3011
3012
3013
	/**
3014
	 * @param  mixed $id
3015
	 * @return void
3016
	 */
3017
	public function handleInlineEdit($id)
3018
	{
3019
		if ($this->inlineEdit) {
3020
			$this->inlineEdit->setItemId($id);
3021
3022
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
3023
3024
			$this['filter']['inline_edit']->addHidden('_id', $id);
3025
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
3026
3027
			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...
3028
				$this->getPresenter()->payload->_datagrid_inline_editing = TRUE;
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...
3029
			}
3030
3031
			$this->redrawItem($id, $primary_where_column);
3032
		}
3033
	}
3034
3035
3036
	/********************************************************************************
3037
	 *                                  INLINE ADD                                  *
3038
	 ********************************************************************************/
3039
3040
3041
	/**
3042
	 * @return InlineEdit
3043
	 */
3044
	public function addInlineAdd()
3045
	{
3046
		$this->inlineAdd = new InlineEdit($this);
3047
3048
		$this->inlineAdd
3049
			->setTitle('ublaboo_datagrid.add')
3050
			->setIcon('plus')
3051
			->setClass('btn btn-xs btn-default');
3052
3053
		return $this->inlineAdd;
3054
	}
3055
3056
3057
	/**
3058
	 * @return InlineEdit|null
3059
	 */
3060
	public function getInlineAdd()
3061
	{
3062
		return $this->inlineAdd;
3063
	}
3064
3065
3066
	/********************************************************************************
3067
	 *                               HIDEABLE COLUMNS                               *
3068
	 ********************************************************************************/
3069
3070
3071
	/**
3072
	 * Can datagrid hide colums?
3073
	 * @return bool
3074
	 */
3075
	public function canHideColumns()
3076
	{
3077
		return (bool) $this->can_hide_columns;
3078
	}
3079
3080
3081
	/**
3082
	 * Order Grid to set columns hideable.
3083
	 * @return static
3084
	 */
3085
	public function setColumnsHideable()
3086
	{
3087
		$this->can_hide_columns = TRUE;
3088
3089
		return $this;
3090
	}
3091
3092
3093
	/********************************************************************************
3094
	 *                                COLUMNS SUMMARY                               *
3095
	 ********************************************************************************/
3096
3097
3098
	/**
3099
	 * Will datagrid show summary in the end?
3100
	 * @return bool
3101
	 */
3102
	public function hasColumnsSummary()
3103
	{
3104 1
		return $this->columnsSummary instanceof ColumnsSummary;
3105
	}
3106
3107
3108
	/**
3109
	 * Set columns to be summarized in the end.
3110
	 * @param array    $columns
3111
	 * @param callable $rowCallback
3112
	 * @return \Ublaboo\DataGrid\ColumnsSummary
3113
	 */
3114
	public function setColumnsSummary(array $columns, $rowCallback = NULL)
3115
	{
3116
		if ($this->hasSomeAggregationFunction()) {
3117
			throw new DataGridException('You can use either ColumnsSummary or AggregationFunctions');
3118
		}
3119
3120
		if (!empty($rowCallback)) {
3121
			if (!is_callable($rowCallback)) {
3122
				throw new \InvalidArgumentException('Row summary callback must be callable');
3123
			}
3124
		}
3125
3126
		$this->columnsSummary = new ColumnsSummary($this, $columns, $rowCallback);
3127
3128
		return $this->columnsSummary;
3129
	}
3130
3131
3132
	/**
3133
	 * @return ColumnsSummary|NULL
3134
	 */
3135
	public function getColumnsSummary()
3136
	{
3137
		return $this->columnsSummary;
3138
	}
3139
3140
3141
	/********************************************************************************
3142
	 *                                   INTERNAL                                   *
3143
	 ********************************************************************************/
3144
3145
3146
	/**
3147
	 * Tell grid filters to by submitted automatically
3148
	 * @param bool $auto
3149
	 */
3150
	public function setAutoSubmit($auto = TRUE)
3151
	{
3152
		$this->auto_submit = (bool) $auto;
3153
3154
		return $this;
3155
	}
3156
3157
3158
	/**
3159
	 * @return bool
3160
	 */
3161
	public function hasAutoSubmit()
3162
	{
3163
		return $this->auto_submit;
3164
	}
3165
3166
3167
	/**
3168
	 * Submit button when no auto-submitting is used
3169
	 * @return Filter\SubmitButton
3170
	 */
3171
	public function getFilterSubmitButton()
3172
	{
3173
		if ($this->hasAutoSubmit()) {
3174
			throw new DataGridException(
3175
				'DataGrid has auto-submit. Turn it off before setting filter submit button.'
3176
			);
3177
		}
3178
3179
		if ($this->filter_submit_button === NULL) {
3180
			$this->filter_submit_button = new Filter\SubmitButton($this);
3181
		}
3182
3183
		return $this->filter_submit_button;
3184
	}
3185
3186
3187
	/********************************************************************************
3188
	 *                                   INTERNAL                                   *
3189
	 ********************************************************************************/
3190
3191
3192
	/**
3193
	 * Get count of columns
3194
	 * @return int
3195
	 */
3196
	public function getColumnsCount()
3197
	{
3198
		$count = sizeof($this->getColumns());
3199
3200
		if (!empty($this->actions)
3201
			|| $this->isSortable()
3202
			|| $this->getItemsDetail()
3203
			|| $this->getInlineEdit()
3204
			|| $this->getInlineAdd()) {
3205
			$count++;
3206
		}
3207
3208
		if ($this->hasGroupActions()) {
3209
			$count++;
3210
		}
3211
3212
		return $count;
3213
	}
3214
3215
3216
	/**
3217
	 * Get primary key of datagrid data source
3218
	 * @return string
3219
	 */
3220
	public function getPrimaryKey()
3221
	{
3222 1
		return $this->primary_key;
3223
	}
3224
3225
3226
	/**
3227
	 * Get set of set columns
3228
	 * @return Column\IColumn[]
3229
	 */
3230
	public function getColumns()
3231
	{
3232 1
		$return = $this->columns;
3233
3234
		try {
3235 1
			$this->getParent();
3236
3237 1
			if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
3238 1
				$columns_to_hide = [];
3239
3240 1
				foreach ($this->columns as $key => $column) {
3241
					if ($column->getDefaultHide()) {
3242
						$columns_to_hide[] = $key;
3243
					}
3244
				}
3245
3246 1
				if (!empty($columns_to_hide)) {
3247
					$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
3248
					$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
3249
				}
3250
			}
3251
3252 1
			$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
3253
3254 1
			foreach ($hidden_columns as $column) {
3255
				if (!empty($this->columns[$column])) {
3256
					$this->columns_visibility[$column] = [
3257
						'visible' => FALSE
3258
					];
3259
3260 1
					unset($return[$column]);
3261
				}
3262
			}
3263
3264
		} catch (DataGridHasToBeAttachedToPresenterComponentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
3265
		}
3266
3267 1
		return $return;
3268
	}
3269
3270
3271
	public function getColumnsVisibility()
3272
	{
3273 1
		$return = $this->columns_visibility;
3274
3275 1
		foreach ($this->columns_visibility as $key => $column) {
3276
			$return[$key]['column'] = $this->columns[$key];
3277
		}
3278
3279 1
		return $return;
3280
	}
3281
3282
3283
	/**
3284
	 * @return PresenterComponent
3285
	 */
3286
	public function getParent()
3287
	{
3288 1
		$parent = parent::getParent();
3289
3290 1
		if (!($parent instanceof PresenterComponent)) {
0 ignored issues
show
Bug introduced by
The class Nette\Application\UI\PresenterComponent does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

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

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

2. Missing use statement

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

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

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

Loading history...
3291
			throw new DataGridHasToBeAttachedToPresenterComponentException(
3292
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
3293
			);
3294
		}
3295
3296 1
		return $parent;
3297
	}
3298
3299
3300
	/**
3301
	 * @return strign
3302
	 */
3303
	public function getSortableParentPath()
3304
	{
3305
		return $this->getParent()->lookupPath(Nette\Application\UI\Control::class, FALSE);
3306
	}
3307
3308
3309
	/**
3310
	 * Some of datagrid columns is hidden by default
3311
	 * @param bool $default_hide
3312
	 */
3313
	public function setSomeColumnDefaultHide($default_hide)
3314
	{
3315
		$this->some_column_default_hide = $default_hide;
3316
	}
3317
3318
3319
	/**
3320
	 * Are some of columns hidden bydefault?
3321
	 */
3322
	public function hasSomeColumnDefaultHide()
3323
	{
3324
		return $this->some_column_default_hide;
3325
	}
3326
3327
3328
	/**
3329
	 * Simply refresh url
3330
	 * @return void
3331
	 */
3332
	public function handleRefreshState()
3333
	{
3334
		$this->findSessionValues();
3335
		$this->findDefaultFilter();
3336
		$this->findDefaultSort();
3337
		$this->findDefaultPerPage();
3338
3339
		$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...
3340
		$this->redrawControl('non-existing-snippet');
3341
	}
3342
3343
}
3344