Completed
Push — master ( 392d04...e48b3d )
by Pavel
02:16
created

DataGrid   F

Complexity

Total Complexity 372

Size/Duplication

Total Lines 3258
Duplicated Lines 4.54 %

Coupling/Cohesion

Components 5
Dependencies 49

Importance

Changes 0
Metric Value
wmc 372
lcom 5
cbo 49
dl 148
loc 3258
rs 0.5217
c 0
b 0
f 0

145 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 34 1
A attached() 0 13 3
D render() 0 101 12
A setRowCallback() 0 6 1
A setPrimaryKey() 0 10 2
A setDataSource() 0 6 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 setDefaultSort() 0 13 2
A findDefaultSort() 0 16 4
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 getSortNext() 0 10 2
B createSorting() 0 21 5
A isTreeView() 0 4 1
B setTreeView() 0 30 4
A hasTreeViewChildrenCallback() 0 4 1
A treeViewChildrenCallback() 0 4 1
A addColumnText() 0 7 2
A addColumnLink() 0 12 4
A addColumnNumber() 0 7 2
A addColumnDateTime() 0 7 2
A addColumnStatus() 0 7 2
A addColumn() 0 10 1
A getColumn() 0 8 2
A removeColumn() 0 5 1
A addColumnCheck() 0 6 2
A addAction() 0 12 3
A addActionCallback() 0 18 3
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
D assableFilters() 0 33 10
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
D setDefaultFilter() 0 38 9
B findDefaultFilter() 0 18 5
F createComponentFilter() 17 86 9
C setFilterContainerDefaults() 0 23 8
D filterSucceeded() 0 106 25
A setOuterFilterRendering() 0 6 1
A hasOuterFilterRendering() 0 4 1
D findSessionValues() 0 68 20
A addExportCallback() 0 8 2
A addExportCsv() 12 12 1
A addExportCsvFiltered() 12 12 1
A addToExports() 0 8 2
A resetExportsLinks() 0 6 2
A addToolbarButton() 0 6 1
A addGroupAction() 0 4 1
A addGroupSelectAction() 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 12 3
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
27
/**
28
 * @method onRedraw()
29
 * @method onRender()
30
 * @method onColumnAdd()
31
 */
32
class DataGrid extends Nette\Application\UI\Control
33
{
34
35
	/**
36
	 * @var callable[]
37
	 */
38
	public $onRedraw;
39
40
	/**
41
	 * @var callable[]
42
	 */
43
	public $onRender = [];
44
45
	/**
46
	 * @var callable[]
47
	 */
48
	public $onExport;
49
50
	/**
51
	 * @var callable[]
52
	 */
53
	public $onColumnAdd;
54
55
	/**
56
	 * @var callable[]
57
	 */
58
	public $onFiltersAssabled;
59
60
	/**
61
	 * @var string
62
	 */
63
	public static $icon_prefix = 'fa fa-';
64
65
	/**
66
	 * Default form method
67
	 * @var string
68
	 */
69
	public static $form_method = 'post';
70
71
	/**
72
	 * When set to TRUE, datagrid throws an exception
73
	 * 	when tring to get related entity within join and entity does not exist
74
	 * @var bool
75
	 */
76
	public $strict_entity_property = FALSE;
77
78
	/**
79
	 * When set to TRUE, datagrid throws an exception
80
	 * 	when tring to set filter value, that does not exist (select, multiselect, etc)
81
	 * @var bool
82
	 */
83
	public $strict_session_filter_values = TRUE;
84
85
	/**
86
	 * @var int
87
	 * @persistent
88
	 */
89
	public $page = 1;
90
91
	/**
92
	 * @var int|string
93
	 * @persistent
94
	 */
95
	public $per_page;
96
97
	/**
98
	 * @var array
99
	 * @persistent
100
	 */
101
	public $sort = [];
102
103
	/**
104
	 * @var array
105
	 */
106
	public $default_sort = [];
107
108
	/**
109
	 * @var array
110
	 */
111
	public $default_filter = [];
112
113
	/**
114
	 * @var bool
115
	 */
116
	public $default_filter_use_on_reset = TRUE;
117
118
	/**
119
	 * @var bool
120
	 */
121
	public $default_sort_use_on_reset = TRUE;
122
123
	/**
124
	 * @var array
125
	 * @persistent
126
	 */
127
	public $filter = [];
128
129
	/**
130
	 * @var callable|null
131
	 */
132
	protected $sort_callback = NULL;
133
134
	/**
135
	 * @var bool
136
	 */
137
	protected $use_happy_components = TRUE;
138
139
	/**
140
	 * @var callable
141
	 */
142
	protected $rowCallback;
143
144
	/**
145
	 * @var array
146
	 */
147
	protected $items_per_page_list;
148
149
	/**
150
	 * @var int
151
	 */
152
	protected $default_per_page;
153
154
	/**
155
	 * @var string
156
	 */
157
	protected $template_file;
158
159
	/**
160
	 * @var Column\IColumn[]
161
	 */
162
	protected $columns = [];
163
164
	/**
165
	 * @var Column\Action[]
166
	 */
167
	protected $actions = [];
168
169
	/**
170
	 * @var GroupAction\GroupActionCollection
171
	 */
172
	protected $group_action_collection;
173
174
	/**
175
	 * @var Filter\Filter[]
176
	 */
177
	protected $filters = [];
178
179
	/**
180
	 * @var Export\Export[]
181
	 */
182
	protected $exports = [];
183
184
	/**
185
	 * @var ToolbarButton[]
186
	 */
187
	protected $toolbar_buttons = [];
188
189
	/**
190
	 * @var DataModel
191
	 */
192
	protected $dataModel;
193
194
	/**
195
	 * @var DataFilter
196
	 */
197
	protected $dataFilter;
198
199
	/**
200
	 * @var string
201
	 */
202
	protected $primary_key = 'id';
203
204
	/**
205
	 * @var bool
206
	 */
207
	protected $do_paginate = TRUE;
208
209
	/**
210
	 * @var bool
211
	 */
212
	protected $csv_export = TRUE;
213
214
	/**
215
	 * @var bool
216
	 */
217
	protected $csv_export_filtered = TRUE;
218
219
	/**
220
	 * @var bool
221
	 */
222
	protected $sortable = FALSE;
223
224
	/**
225
	 * @var bool
226
	 */
227
	protected $multiSort = FALSE;
228
229
	/**
230
	 * @var string
231
	 */
232
	protected $sortable_handler = 'sort!';
233
234
	/**
235
	 * @var string
236
	 */
237
	protected $original_template;
238
239
	/**
240
	 * @var array
241
	 */
242
	protected $redraw_item;
243
244
	/**
245
	 * @var mixed
246
	 */
247
	protected $translator;
248
249
	/**
250
	 * @var bool
251
	 */
252
	protected $force_filter_active;
253
254
	/**
255
	 * @var callable
256
	 */
257
	protected $tree_view_children_callback;
258
259
	/**
260
	 * @var callable
261
	 */
262
	protected $tree_view_has_children_callback;
263
264
	/**
265
	 * @var string
266
	 */
267
	protected $tree_view_has_children_column;
268
269
	/**
270
	 * @var bool
271
	 */
272
	protected $outer_filter_rendering = FALSE;
273
274
	/**
275
	 * @var array
276
	 */
277
	protected $columns_export_order = [];
278
279
	/**
280
	 * @var bool
281
	 */
282
	protected $remember_state = TRUE;
283
284
	/**
285
	 * @var bool
286
	 */
287
	protected $refresh_url = TRUE;
288
289
	/**
290
	 * @var Nette\Http\SessionSection
291
	 */
292
	protected $grid_session;
293
294
	/**
295
	 * @var Column\ItemDetail
296
	 */
297
	protected $items_detail;
298
299
	/**
300
	 * @var array
301
	 */
302
	protected $row_conditions = [
303
		'group_action' => FALSE,
304
		'action' => []
305
	];
306
307
	/**
308
	 * @var array
309
	 */
310
	protected $column_callbacks = [];
311
312
	/**
313
	 * @var bool
314
	 */
315
	protected $can_hide_columns = FALSE;
316
317
	/**
318
	 * @var array
319
	 */
320
	protected $columns_visibility = [];
321
322
	/**
323
	 * @var InlineEdit
324
	 */
325
	protected $inlineEdit;
326
327
	/**
328
	 * @var InlineEdit
329
	 */
330
	protected $inlineAdd;
331
332
	/**
333
	 * @var bool
334
	 */
335
	protected $snippets_set = FALSE;
336
337
	/**
338
	 * @var bool
339
	 */
340
	protected $some_column_default_hide = FALSE;
341
342
	/**
343
	 * @var ColumnsSummary
344
	 */
345
	protected $columnsSummary;
346
347
	/**
348
	 * @var bool
349
	 */
350
	protected $auto_submit = TRUE;
351
352
	/**
353
	 * @var Filter\SubmitButton|NULL
354
	 */
355
	protected $filter_submit_button = NULL;
356
357
	/**
358
	 * @var bool
359
	 */
360
	protected $has_column_reset = TRUE;
361
362
363
	/**
364
	 * @param Nette\ComponentModel\IContainer|NULL $parent
365
	 * @param string                               $name
366
	 */
367
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
368
	{
369
		parent::__construct($parent, $name);
370
371
		$this->monitor('Nette\Application\UI\Presenter');
372
373
		/**
374
		 * Try to find previous filters, pagination, per_page and other values in session
375
		 */
376
		$this->onRender[] = [$this, 'findSessionValues'];
377
		$this->onExport[] = [$this, 'findSessionValues'];
378
379
		/**
380
		 * Find default filter values
381
		 */
382
		$this->onRender[] = [$this, 'findDefaultFilter'];
383
		$this->onExport[] = [$this, 'findDefaultFilter'];
384
385
		/**
386
		 * Find default sort
387
		 */
388
		$this->onRender[] = [$this, 'findDefaultSort'];
389
		$this->onExport[] = [$this, 'findDefaultSort'];
390
391
		/**
392
		 * Find default items per page
393
		 */
394
		$this->onRender[] = [$this, 'findDefaultPerPage'];
395
396
		/**
397
		 * Notify about that json js extension
398
		 */
399
		$this->onFiltersAssabled[] = [$this, 'sendNonEmptyFiltersInPayload'];
400
	}
401
402
403
	/**
404
	 * {inheritDoc}
405
	 * @return void
406
	 */
407
	public function attached($presenter)
408
	{
409
		parent::attached($presenter);
410
411
		if ($presenter instanceof Nette\Application\UI\Presenter) {
412
			/**
413
			 * Get session
414
			 */
415
			if ($this->remember_state) {
416
				$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...
417
			}
418
		}
419
	}
420
421
422
	/********************************************************************************
423
	 *                                  RENDERING                                   *
424
	 ********************************************************************************/
425
426
427
	/**
428
	 * Render template
429
	 * @return void
430
	 */
431
	public function render()
432
	{
433
		/**
434
		 * Check whether datagrid has set some columns, initiated data source, etc
435
		 */
436
		if (!($this->dataModel instanceof DataModel)) {
437
			throw new DataGridException('You have to set a data source first.');
438
		}
439
440
		if (empty($this->columns)) {
441
			throw new DataGridException('You have to add at least one column.');
442
		}
443
444
		$this->getTemplate()->setTranslator($this->getTranslator());
445
446
		/**
447
		 * Invoke possible events
448
		 */
449
		$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...
450
451
		/**
452
		 * Prepare data for rendering (datagrid may render just one item)
453
		 */
454
		$rows = [];
455
456
		if (!empty($this->redraw_item)) {
457
			$items = $this->dataModel->filterRow($this->redraw_item);
458
		} else {
459
			$items = Nette\Utils\Callback::invokeArgs(
460
				[$this->dataModel, 'filterData'],
461
				[
462
					$this->getPaginator(),
463
					$this->createSorting($this->sort, $this->sort_callback),
464
					$this->assableFilters()
465
				]
466
			);
467
		}
468
469
		$callback = $this->rowCallback ?: NULL;
470
		$hasGroupActionOnRows = FALSE;
471
472
		foreach ($items as $item) {
473
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
474
475
			if (!$hasGroupActionOnRows && $row->hasGroupAction()){
476
				$hasGroupActionOnRows = TRUE;
477
			}
478
			
479
			if ($callback) {
480
				$callback($item, $row->getControl());
481
			}
482
483
			/**
484
			 * Walkaround for item snippet - snippet is the <tr> element and its class has to be also updated
485
			 */
486
			if (!empty($this->redraw_item)) {
487
				$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...
488
				$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...
489
			}
490
		}
491
492
		if ($hasGroupActionOnRows){
493
			$hasGroupActionOnRows = $this->hasGroupActions();
494
		}
495
496
		if ($this->isTreeView()) {
497
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
498
		}
499
500
		$this->getTemplate()->add('rows', $rows);
501
502
		$this->getTemplate()->add('columns', $this->getColumns());
503
		$this->getTemplate()->add('actions', $this->actions);
504
		$this->getTemplate()->add('exports', $this->exports);
505
		$this->getTemplate()->add('filters', $this->filters);
506
		$this->getTemplate()->add('toolbar_buttons', $this->toolbar_buttons);
507
508
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
509
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
510
		//$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...
511
		$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...
512
		$this->getTemplate()->add('items_detail', $this->items_detail);
513
		$this->getTemplate()->add('columns_visibility', $this->getColumnsVisibility());
514
		$this->getTemplate()->add('columnsSummary', $this->columnsSummary);
515
516
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
517
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
518
519
		$this->getTemplate()->add('hasGroupActionOnRows', $hasGroupActionOnRows);
520
521
		/**
522
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
523
		 */
524
		$this->getTemplate()->add('filter', $this['filter']);
525
526
		/**
527
		 * Set template file and render it
528
		 */
529
		$this->getTemplate()->setFile($this->getTemplateFile());
530
		$this->getTemplate()->render();
531
	}
532
533
534
	/********************************************************************************
535
	 *                                 ROW CALLBACK                                 *
536
	 ********************************************************************************/
537
538
539
	/**
540
	 * Each row can be modified with user callback
541
	 * @param  callable  $callback
542
	 * @return static
543
	 */
544
	public function setRowCallback(callable $callback)
545
	{
546
		$this->rowCallback = $callback;
547
548
		return $this;
549
	}
550
551
552
	/********************************************************************************
553
	 *                                 DATA SOURCE                                  *
554
	 ********************************************************************************/
555
556
557
	/**
558
	 * By default ID, you can change that
559
	 * @param string $primary_key
560
	 * @return static
561
	 */
562
	public function setPrimaryKey($primary_key)
563
	{
564
		if ($this->dataModel instanceof DataModel) {
565
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
566
		}
567
568
		$this->primary_key = $primary_key;
569
570
		return $this;
571
	}
572
573
574
	/**
575
	 * Set Grid data source
576
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
577
	 * @return static
578
	 */
579
	public function setDataSource($source)
580
	{
581
		$this->dataModel = new DataModel($source, $this->primary_key);
582
583
		return $this;
584
	}
585
586
587
	/**
588
	 * @return DataSource\IDataSource|NULL
589
	 */
590
	public function getDataSource()
591
	{
592
		if (!$this->dataModel) {
593
			return NULL;
594
		}
595
596
		return $this->dataModel->getDataSource();
597
	}
598
599
600
	/********************************************************************************
601
	 *                                  TEMPLATING                                  *
602
	 ********************************************************************************/
603
604
605
	/**
606
	 * Set custom template file to render
607
	 * @param string $template_file
608
	 * @return static
609
	 */
610
	public function setTemplateFile($template_file)
611
	{
612
		$this->template_file = $template_file;
613
614
		return $this;
615
	}
616
617
618
	/**
619
	 * Get DataGrid template file
620
	 * @return string
621
	 * @return static
622
	 */
623
	public function getTemplateFile()
624
	{
625
		return $this->template_file ?: $this->getOriginalTemplateFile();
626
	}
627
628
629
	/**
630
	 * Get DataGrid original template file
631
	 * @return string
632
	 */
633
	public function getOriginalTemplateFile()
634
	{
635
		return __DIR__.'/templates/datagrid.latte';
636
	}
637
638
639
	/**
640
	 * Tell datagrid wheteher to use or not happy components
641
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
642
	 * @return void|bool
643
	 */
644
	public function useHappyComponents($use = NULL)
645
	{
646
		if (NULL === $use) {
647
			return $this->use_happy_components;
648
		}
649
650
		$this->use_happy_components = (bool) $use;
651
	}
652
653
654
	/********************************************************************************
655
	 *                                   SORTING                                    *
656
	 ********************************************************************************/
657
658
659
	/**
660
	 * Set default sorting
661
	 * @param array $sort
662
	 * @param bool  $use_on_reset
663
	 * @return static
664
	 */
665
	public function setDefaultSort($sort, $use_on_reset = TRUE)
666
	{
667
		if (is_string($sort)) {
668
			$sort = [$sort => 'ASC'];
669
		} else {
670
			$sort = (array) $sort;
671
		}
672
673
		$this->default_sort = $sort;
674
		$this->default_sort_use_on_reset = (bool) $use_on_reset;
675
676
		return $this;
677
	}
678
679
680
	/**
681
	 * User may set default sorting, apply it
682
	 * @return void
683
	 */
684
	public function findDefaultSort()
685
	{
686
		if (!empty($this->sort)) {
687
			return;
688
		}
689
690
		if ($this->getSessionData('_grid_has_sorted')) {
691
			return;
692
		}
693
694
		if (!empty($this->default_sort)) {
695
			$this->sort = $this->default_sort;
696
		}
697
698
		$this->saveSessionData('_grid_sort', $this->sort);
699
	}
700
701
702
	/**
703
	 * Set grido to be sortable
704
	 * @param bool $sortable
705
	 * @return static
706
	 */
707
	public function setSortable($sortable = TRUE)
708
	{
709
		if ($this->getItemsDetail()) {
710
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
711
		}
712
713
		$this->sortable = (bool) $sortable;
714
715
		return $this;
716
	}
717
718
719
	/**
720
	 * Tell whether DataGrid is sortable
721
	 * @return bool
722
	 */
723
	public function isSortable()
724
	{
725
		return $this->sortable;
726
	}
727
728
729
	/**
730
	 * Enable multi-sorting capability
731
	 * @param bool  $multiSort
732
	 * @return void
733
	 */
734
	public function setMultiSortEnabled($multiSort = TRUE)
735
	{
736
		$this->multiSort = (bool) $multiSort;
737
	}
738
739
740
	/**
741
	 * Tell wether DataGrid can be sorted by multiple columns
742
	 * @return bool
743
	 */
744
	public function isMultiSortEnabled()
745
	{
746
		return $this->multiSort;
747
	}
748
749
750
	/**
751
	 * Set sortable handle
752
	 * @param string $handler
753
	 * @return static
754
	 */
755
	public function setSortableHandler($handler = 'sort!')
756
	{
757
		$this->sortable_handler = (string) $handler;
758
759
		return $this;
760
	}
761
762
	/**
763
	 * Return sortable handle name
764
	 * @return string
765
	 */
766
	public function getSortableHandler()
767
	{
768
		return $this->sortable_handler;
769
	}
770
771
772
	/**
773
	 * @param Column  $column
774
	 * @return array
775
	 * @internal
776
	 */
777
	public function getSortNext(\Ublaboo\DataGrid\Column\Column $column)
778
	{
779
		$sort = $column->getSortNext();
780
781
		if ($this->isMultiSortEnabled()) {
782
			$sort = array_merge($this->sort, $sort);
783
		}
784
785
		return array_filter($sort);
786
	}
787
788
789
	/**
790
	 * @param  array         $sort
791
	 * @param  callable|NULL $sort_callback
792
	 * @return void
793
	 */
794
	protected function createSorting(array $sort, callable $sort_callback = NULL)
795
	{
796
		foreach ($sort as $key => $order) {
797
			unset($sort[$key]);
798
799
			try {
800
				$column = $this->getColumn($key);
801
802
			} catch (DataGridColumnNotFoundException $e) {
803
				continue;
804
			}
805
806
			$sort[$column->getSortingColumn()] = $order;
807
		}
808
809
		if (!$sort_callback && isset($column)) {
810
			$sort_callback = $column->getSortableCallback();
811
		}
812
813
		return new Sorting($sort, $sort_callback);
814
	}
815
816
817
	/********************************************************************************
818
	 *                                  TREE VIEW                                   *
819
	 ********************************************************************************/
820
821
822
	/**
823
	 * Is tree view set?
824
	 * @return boolean
825
	 */
826
	public function isTreeView()
827
	{
828
		return (bool) $this->tree_view_children_callback;
829
	}
830
831
832
	/**
833
	 * Setting tree view
834
	 * @param callable $get_children_callback
835
	 * @param string|callable $tree_view_has_children_column
836
	 * @return static
837
	 */
838
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
839
	{
840
		if (!is_callable($get_children_callback)) {
841
			throw new DataGridException(
842
				'Parameters to method DataGrid::setTreeView must be of type callable'
843
			);
844
		}
845
846
		if (is_callable($tree_view_has_children_column)) {
847
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
848
			$tree_view_has_children_column = NULL;
849
		}
850
851
		$this->tree_view_children_callback = $get_children_callback;
852
		$this->tree_view_has_children_column = $tree_view_has_children_column;
853
854
		/**
855
		 * TUrn off pagination
856
		 */
857
		$this->setPagination(FALSE);
858
859
		/**
860
		 * Set tree view template file
861
		 */
862
		if (!$this->template_file) {
863
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
864
		}
865
866
		return $this;
867
	}
868
869
870
	/**
871
	 * Is tree view children callback set?
872
	 * @return boolean
873
	 */
874
	public function hasTreeViewChildrenCallback()
875
	{
876
		return is_callable($this->tree_view_has_children_callback);
877
	}
878
879
880
	/**
881
	 * @param  mixed $item
882
	 * @return boolean
883
	 */
884
	public function treeViewChildrenCallback($item)
885
	{
886
		return call_user_func($this->tree_view_has_children_callback, $item);
887
	}
888
889
890
	/********************************************************************************
891
	 *                                    COLUMNS                                   *
892
	 ********************************************************************************/
893
894
895
	/**
896
	 * Add text column with no other formating
897
	 * @param  string      $key
898
	 * @param  string      $name
899
	 * @param  string|null $column
900
	 * @return Column\ColumnText
901
	 */
902
	public function addColumnText($key, $name, $column = NULL)
903
	{
904
		$this->addColumnCheck($key);
905
		$column = $column ?: $key;
906
907
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
908
	}
909
910
911
	/**
912
	 * Add column with link
913
	 * @param  string      $key
914
	 * @param  string      $name
915
	 * @param  string|null $column
916
	 * @return Column\ColumnLink
917
	 */
918
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
919
	{
920
		$this->addColumnCheck($key);
921
		$column = $column ?: $key;
922
		$href = $href ?: $key;
923
924
		if (NULL === $params) {
925
			$params = [$this->primary_key];
926
		}
927
928
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
929
	}
930
931
932
	/**
933
	 * Add column with possible number formating
934
	 * @param  string      $key
935
	 * @param  string      $name
936
	 * @param  string|null $column
937
	 * @return Column\ColumnNumber
938
	 */
939
	public function addColumnNumber($key, $name, $column = NULL)
940
	{
941
		$this->addColumnCheck($key);
942
		$column = $column ?: $key;
943
944
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
945
	}
946
947
948
	/**
949
	 * Add column with date formating
950
	 * @param  string      $key
951
	 * @param  string      $name
952
	 * @param  string|null $column
953
	 * @return Column\ColumnDateTime
954
	 */
955
	public function addColumnDateTime($key, $name, $column = NULL)
956
	{
957
		$this->addColumnCheck($key);
958
		$column = $column ?: $key;
959
960
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
961
	}
962
963
964
	/**
965
	 * Add column status
966
	 * @param  string      $key
967
	 * @param  string      $name
968
	 * @param  string|null $column
969
	 * @return Column\ColumnStatus
970
	 */
971
	public function addColumnStatus($key, $name, $column = NULL)
972
	{
973
		$this->addColumnCheck($key);
974
		$column = $column ?: $key;
975
976
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
977
	}
978
979
980
	/**
981
	 * @param string $key
982
	 * @param Column\Column $column
983
	 * @return Column\Column
984
	 */
985
	protected function addColumn($key, Column\Column $column)
986
	{
987
		$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...
988
989
		$this->columns_visibility[$key] = [
990
			'visible' => TRUE
991
		];
992
993
		return $this->columns[$key] = $column;
994
	}
995
996
997
	/**
998
	 * Return existing column
999
	 * @param  string $key
1000
	 * @return Column\Column
1001
	 * @throws DataGridException
1002
	 */
1003
	public function getColumn($key)
1004
	{
1005
		if (!isset($this->columns[$key])) {
1006
			throw new DataGridColumnNotFoundException("There is no column at key [$key] defined.");
1007
		}
1008
1009
		return $this->columns[$key];
1010
	}
1011
1012
1013
	/**
1014
	 * Remove column
1015
	 * @param string $key
1016
	 * @return void
1017
	 */
1018
	public function removeColumn($key)
1019
	{
1020
		unset($this->columns_visibility[$key]);
1021
		unset($this->columns[$key]);
1022
	}
1023
1024
1025
	/**
1026
	 * Check whether given key already exists in $this->columns
1027
	 * @param  string $key
1028
	 * @throws DataGridException
1029
	 */
1030
	protected function addColumnCheck($key)
1031
	{
1032
		if (isset($this->columns[$key])) {
1033
			throw new DataGridException("There is already column at key [$key] defined.");
1034
		}
1035
	}
1036
1037
1038
	/********************************************************************************
1039
	 *                                    ACTIONS                                   *
1040
	 ********************************************************************************/
1041
1042
1043
	/**
1044
	 * Create action
1045
	 * @param string     $key
1046
	 * @param string     $name
1047
	 * @param string     $href
1048
	 * @param array|null $params
1049
	 * @return Column\Action
1050
	 */
1051
	public function addAction($key, $name, $href = NULL, array $params = NULL)
1052
	{
1053
		$this->addActionCheck($key);
1054
1055
		$href = $href ?: $key;
1056
1057
		if (NULL === $params) {
1058
			$params = [$this->primary_key];
1059
		}
1060
1061
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
1062
	}
1063
1064
1065
	/**
1066
	 * Create action callback
1067
	 * @param string     $key
1068
	 * @param string     $name
1069
	 * @return Column\Action
1070
	 */
1071
	public function addActionCallback($key, $name, $callback = NULL)
1072
	{
1073
		$this->addActionCheck($key);
1074
1075
		$params = ['__id' => $this->primary_key];
1076
1077
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
1078
1079
		if ($callback) {
1080
			if (!is_callable($callback)) {
1081
				throw new DataGridException('ActionCallback callback has to be callable.');
1082
			}
1083
1084
			$action->onClick[] = $callback;
1085
		}
1086
1087
		return $action;
1088
	}
1089
1090
1091
	/**
1092
	 * @param string $key
1093
	 */
1094
	public function addMultiAction($key, $name)
1095
	{
1096
		$this->addActionCheck($key);
1097
1098
		$this->actions[$key] = $action = new Column\MultiAction($this, $name);
1099
1100
		return $action;
1101
	}
1102
1103
1104
	/**
1105
	 * Get existing action
1106
	 * @param  string       $key
1107
	 * @return Column\Action
1108
	 * @throws DataGridException
1109
	 */
1110 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...
1111
	{
1112
		if (!isset($this->actions[$key])) {
1113
			throw new DataGridException("There is no action at key [$key] defined.");
1114
		}
1115
1116
		return $this->actions[$key];
1117
	}
1118
1119
1120
	/**
1121
	 * Remove action
1122
	 * @param string $key
1123
	 * @return void
1124
	 */
1125
	public function removeAction($key)
1126
	{
1127
		unset($this->actions[$key]);
1128
	}
1129
1130
1131
	/**
1132
	 * Check whether given key already exists in $this->filters
1133
	 * @param  string $key
1134
	 * @throws DataGridException
1135
	 */
1136
	protected function addActionCheck($key)
1137
	{
1138
		if (isset($this->actions[$key])) {
1139
			throw new DataGridException("There is already action at key [$key] defined.");
1140
		}
1141
	}
1142
1143
1144
	/********************************************************************************
1145
	 *                                    FILTERS                                   *
1146
	 ********************************************************************************/
1147
1148
1149
	/**
1150
	 * Add filter fot text search
1151
	 * @param string       $key
1152
	 * @param string       $name
1153
	 * @param array|string $columns
1154
	 * @return Filter\FilterText
1155
	 * @throws DataGridException
1156
	 */
1157
	public function addFilterText($key, $name, $columns = NULL)
1158
	{
1159
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
1160
1161
		if (!is_array($columns)) {
1162
			throw new DataGridException("Filter Text can accept only array or string.");
1163
		}
1164
1165
		$this->addFilterCheck($key);
1166
1167
		return $this->filters[$key] = new Filter\FilterText($this, $key, $name, $columns);
1168
	}
1169
1170
1171
	/**
1172
	 * Add select box filter
1173
	 * @param string $key
1174
	 * @param string $name
1175
	 * @param array  $options
1176
	 * @param string $column
1177
	 * @return Filter\FilterSelect
1178
	 * @throws DataGridException
1179
	 */
1180 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...
1181
	{
1182
		$column = $column ?: $key;
1183
1184
		if (!is_string($column)) {
1185
			throw new DataGridException("Filter Select can only filter in one column.");
1186
		}
1187
1188
		$this->addFilterCheck($key);
1189
1190
		return $this->filters[$key] = new Filter\FilterSelect($this, $key, $name, $options, $column);
1191
	}
1192
1193
1194
	/**
1195
	 * Add multi select box filter
1196
	 * @param string $key
1197
	 * @param string $name
1198
	 * @param array  $options
1199
	 * @param string $column
1200
	 * @return Filter\FilterSelect
1201
	 * @throws DataGridException
1202
	 */
1203 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...
1204
	{
1205
		$column = $column ?: $key;
1206
1207
		if (!is_string($column)) {
1208
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1209
		}
1210
1211
		$this->addFilterCheck($key);
1212
1213
		return $this->filters[$key] = new Filter\FilterMultiSelect($this, $key, $name, $options, $column);
1214
	}
1215
1216
1217
	/**
1218
	 * Add datepicker filter
1219
	 * @param string $key
1220
	 * @param string $name
1221
	 * @param string $column
1222
	 * @return Filter\FilterDate
1223
	 * @throws DataGridException
1224
	 */
1225 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...
1226
	{
1227
		$column = $column ?: $key;
1228
1229
		if (!is_string($column)) {
1230
			throw new DataGridException("FilterDate can only filter in one column.");
1231
		}
1232
1233
		$this->addFilterCheck($key);
1234
1235
		return $this->filters[$key] = new Filter\FilterDate($this, $key, $name, $column);
1236
	}
1237
1238
1239
	/**
1240
	 * Add range filter (from - to)
1241
	 * @param string $key
1242
	 * @param string $name
1243
	 * @param string $column
1244
	 * @return Filter\FilterRange
1245
	 * @throws DataGridException
1246
	 */
1247 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...
1248
	{
1249
		$column = $column ?: $key;
1250
1251
		if (!is_string($column)) {
1252
			throw new DataGridException("FilterRange can only filter in one column.");
1253
		}
1254
1255
		$this->addFilterCheck($key);
1256
1257
		return $this->filters[$key] = new Filter\FilterRange($this, $key, $name, $column, $name_second);
1258
	}
1259
1260
1261
	/**
1262
	 * Add datepicker filter (from - to)
1263
	 * @param string $key
1264
	 * @param string $name
1265
	 * @param string $column
1266
	 * @return Filter\FilterDateRange
1267
	 * @throws DataGridException
1268
	 */
1269 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...
1270
	{
1271
		$column = $column ?: $key;
1272
1273
		if (!is_string($column)) {
1274
			throw new DataGridException("FilterDateRange can only filter in one column.");
1275
		}
1276
1277
		$this->addFilterCheck($key);
1278
1279
		return $this->filters[$key] = new Filter\FilterDateRange($this, $key, $name, $column, $name_second);
1280
	}
1281
1282
1283
	/**
1284
	 * Check whether given key already exists in $this->filters
1285
	 * @param  string $key
1286
	 * @throws DataGridException
1287
	 */
1288
	protected function addFilterCheck($key)
1289
	{
1290
		if (isset($this->filters[$key])) {
1291
			throw new DataGridException("There is already action at key [$key] defined.");
1292
		}
1293
	}
1294
1295
1296
	/**
1297
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1298
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1299
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1300
	 */
1301
	public function assableFilters()
1302
	{
1303
		foreach ($this->filter as $key => $value) {
1304
			if (!isset($this->filters[$key])) {
1305
				$this->deleteSessionData($key);
1306
1307
				continue;
1308
			}
1309
1310
			if (is_array($value) || $value instanceof \Traversable) {
1311
				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...
1312
					$this->filters[$key]->setValue($value);
1313
				}
1314
			} else {
1315
				if ($value !== '' && $value !== NULL) {
1316
					$this->filters[$key]->setValue($value);
1317
				}
1318
			}
1319
		}
1320
1321
		foreach ($this->columns as $key => $column) {
1322
			if (isset($this->sort[$key])) {
1323
				$column->setSort($this->sort);
1324
			}
1325
		}
1326
1327
		/**
1328
		 * Invoke possible events
1329
		 */
1330
		$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...
1331
1332
		return $this->filters;
1333
	}
1334
1335
1336
	/**
1337
	 * Remove filter
1338
	 * @param string $key
1339
	 * @return void
1340
	 */
1341
	public function removeFilter($key)
1342
	{
1343
		unset($this->filters[$key]);
1344
	}
1345
1346
1347
	/**
1348
	 * Get defined filter
1349
	 * @param  string $key
1350
	 * @return Filter\Filter
1351
	 */
1352
	public function getFilter($key)
1353
	{
1354
		if (!isset($this->filters[$key])) {
1355
			throw new DataGridException("Filter [{$key}] is not defined");
1356
		}
1357
1358
		return $this->filters[$key];
1359
	}
1360
1361
1362
	/**
1363
	 * @param bool $strict
1364
	 */
1365
	public function setStrictSessionFilterValues($strict = TRUE)
1366
	{
1367
		$this->strict_session_filter_values = (bool) $strict;
1368
	}
1369
1370
1371
	/********************************************************************************
1372
	 *                                  FILTERING                                   *
1373
	 ********************************************************************************/
1374
1375
1376
	/**
1377
	 * Is filter active?
1378
	 * @return boolean
1379
	 */
1380
	public function isFilterActive()
1381
	{
1382
		$is_filter = ArraysHelper::testTruthy($this->filter);
1383
1384
		return ($is_filter) || $this->force_filter_active;
1385
	}
1386
1387
1388
	/**
1389
	 * Tell that filter is active from whatever reasons
1390
	 * return static
1391
	 */
1392
	public function setFilterActive()
1393
	{
1394
		$this->force_filter_active = TRUE;
1395
1396
		return $this;
1397
	}
1398
1399
1400
	/**
1401
	 * Set filter values (force - overwrite user data)
1402
	 * @param array $filter
1403
	 * @return static
1404
	 */
1405
	public function setFilter(array $filter)
1406
	{
1407
		$this->filter = $filter;
1408
1409
		$this->saveSessionData('_grid_has_filtered', 1);
1410
1411
		return $this;
1412
	}
1413
1414
1415
	/**
1416
	 * If we want to sent some initial filter
1417
	 * @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...
1418
	 * @param bool  $use_on_reset
1419
	 * @return static
1420
	 */
1421
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1422
	{
1423
		foreach ($default_filter as $key => $value) {
1424
			$filter = $this->getFilter($key);
1425
1426
			if (!$filter) {
1427
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1428
			}
1429
1430
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1431
				throw new DataGridException(
1432
					"Default value of filter [$key] - MultiSelect has to be an array"
1433
				);
1434
			}
1435
1436
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1437
				if (!is_array($value)) {
1438
					throw new DataGridException(
1439
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1440
					);
1441
				}
1442
1443
				$temp = $value;
1444
				unset($temp['from'], $temp['to']);
1445
1446
				if (!empty($temp)) {
1447
					throw new DataGridException(
1448
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1449
					);
1450
				}
1451
			}
1452
		}
1453
1454
		$this->default_filter = $default_filter;
1455
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1456
1457
		return $this;
1458
	}
1459
1460
1461
	/**
1462
	 * User may set default filter, find it
1463
	 * @return void
1464
	 */
1465
	public function findDefaultFilter()
1466
	{
1467
		if (!empty($this->filter)) {
1468
			return;
1469
		}
1470
1471
		if ($this->getSessionData('_grid_has_filtered')) {
1472
			return;
1473
		}
1474
1475
		if (!empty($this->default_filter)) {
1476
			$this->filter = $this->default_filter;
1477
		}
1478
1479
		foreach ($this->filter as $key => $value) {
1480
			$this->saveSessionData($key, $value);
1481
		}
1482
	}
1483
1484
1485
	/**
1486
	 * FilterAndGroupAction form factory
1487
	 * @return Form
1488
	 */
1489
	public function createComponentFilter()
1490
	{
1491
		$form = new Form($this, 'filter');
1492
1493
		$form->setMethod(static::$form_method);
1494
1495
		$form->setTranslator($this->getTranslator());
1496
1497
		/**
1498
		 * InlineEdit part
1499
		 */
1500
		$inline_edit_container = $form->addContainer('inline_edit');
1501
1502 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...
1503
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1504
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1505
				->setValidationScope(FALSE);
1506
1507
			$this->inlineEdit->onControlAdd($inline_edit_container);
1508
			$this->inlineEdit->onControlAfterAdd($inline_edit_container);
1509
		}
1510
1511
		/**
1512
		 * InlineAdd part
1513
		 */
1514
		$inline_add_container = $form->addContainer('inline_add');
1515
1516 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...
1517
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1518
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1519
				->setValidationScope(FALSE)
1520
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1521
1522
			$this->inlineAdd->onControlAdd($inline_add_container);
1523
			$this->inlineAdd->onControlAfterAdd($inline_add_container);
1524
		}
1525
1526
		/**
1527
		 * ItemDetail form part
1528
		 */
1529
		$items_detail_form = $this->getItemDetailForm();
1530
1531
		if ($items_detail_form instanceof Nette\Forms\Container) {
1532
			$form['items_detail_form'] = $items_detail_form;
1533
		}
1534
1535
		/**
1536
		 * Filter part
1537
		 */
1538
		$filter_container = $form->addContainer('filter');
1539
1540
		foreach ($this->filters as $filter) {
1541
			$filter->addToFormContainer($filter_container);
1542
		}
1543
1544
		if (!$this->hasAutoSubmit()) {
1545
			$filter_container['submit'] = $this->getFilterSubmitButton();
1546
		}
1547
1548
		/**
1549
		 * Group action part
1550
		 */
1551
		$group_action_container = $form->addContainer('group_action');
1552
1553
		if ($this->hasGroupActions()) {
1554
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1555
		}
1556
1557
		if (!$form->isSubmitted()) {
1558
			$this->setFilterContainerDefaults($form['filter'], $this->filter);
1559
		}
1560
1561
		/**
1562
		 * Per page part
1563
		 */
1564
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1565
			->setTranslator(NULL);
1566
1567
		if (!$form->isSubmitted()) {
1568
			$form['per_page']->setValue($this->getPerPage());
1569
		}
1570
1571
		$form->addSubmit('per_page_submit', '');
1572
		
1573
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1574
	}
1575
1576
1577
	/**
1578
	 * @param  Nette\Forms\Container  $container
1579
	 * @param  array  $values
1580
	 * @return void
1581
	 */
1582
	public function setFilterContainerDefaults(Nette\Forms\Container $container, array $values)
1583
	{
1584
		foreach ($container->getComponents() as $key => $control) {
1585
			if (!isset($values[$key]) || !method_exists($control, 'setValue')) {
1586
				continue;
1587
			}
1588
1589
			$value = $values[$key];
1590
1591
			if ($value instanceof \DateTime && ($filter = $this->getFilter($key)) instanceof IFilterDate) {
1592
				$value = $value->format($filter->getPhpFormat());
1593
			}
1594
1595
			try {
1596
				$control->setValue($value);
1597
1598
			} catch (Nette\InvalidArgumentException $e) {
1599
				if ($this->strict_session_filter_values) {
1600
					throw $e;
1601
				}
1602
			}
1603
		}
1604
	}
1605
1606
1607
	/**
1608
	 * Set $this->filter values after filter form submitted
1609
	 * @param  Form $form
1610
	 * @return void
1611
	 */
1612
	public function filterSucceeded(Form $form)
1613
	{
1614
		if ($this->snippets_set) {
1615
			return;
1616
		}
1617
1618
		$values = $form->getValues();
1619
1620
		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...
1621
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1622
				return;
1623
			}
1624
		}
1625
1626
		/**
1627
		 * Per page
1628
		 */
1629
		$this->saveSessionData('_grid_per_page', $values->per_page);
1630
		$this->per_page = $values->per_page;
1631
1632
		/**
1633
		 * Inline edit
1634
		 */
1635
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1636
			$edit = $form['inline_edit'];
1637
1638
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1639
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1640
				$primary_where_column = $form->getHttpData(
1641
					Form::DATA_LINE,
1642
					'inline_edit[_primary_where_column]'
1643
				);
1644
1645
				if ($edit['submit']->isSubmittedBy()) {
1646
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1647
					$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...
1648
					$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...
1649
				} else {
1650
					$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...
1651
					$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...
1652
				}
1653
1654
				if ($edit['submit']->isSubmittedBy() && !empty($this->inlineEdit->onCustomRedraw)) {
1655
					$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...
1656
				} else {
1657
					$this->redrawItem($id, $primary_where_column);
1658
				}
1659
1660
				return;
1661
			}
1662
		}
1663
1664
		/**
1665
		 * Inline add
1666
		 */
1667
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1668
			$add = $form['inline_add'];
1669
1670
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1671
				if ($add['submit']->isSubmittedBy()) {
1672
					$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...
1673
1674
					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...
1675
						$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...
1676
					}
1677
				}
1678
1679
				return;
1680
			}
1681
		}
1682
1683
		/**
1684
		 * Filter itself
1685
		 */
1686
		$values = $values['filter'];
1687
1688
		foreach ($values as $key => $value) {
1689
			/**
1690
			 * Session stuff
1691
			 */
1692
			$this->saveSessionData($key, $value);
1693
1694
			/**
1695
			 * Other stuff
1696
			 */
1697
			$this->filter[$key] = $value;
1698
		}
1699
1700
		if (!empty($values)) {
1701
			$this->saveSessionData('_grid_has_filtered', 1);
1702
		}
1703
1704
		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...
1705
			$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...
1706
1707
			foreach ($this->columns as $key => $column) {
1708
				if ($column->isSortable()) {
1709
					$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...
1710
						'sort' => $column->getSortNext()
1711
					]);
1712
				}
1713
			}
1714
		}
1715
1716
		$this->reload();
1717
	}
1718
1719
1720
	/**
1721
	 * Should be datagrid filters rendered separately?
1722
	 * @param boolean $out
1723
	 * @return static
1724
	 */
1725
	public function setOuterFilterRendering($out = TRUE)
1726
	{
1727
		$this->outer_filter_rendering = (bool) $out;
1728
1729
		return $this;
1730
	}
1731
1732
1733
	/**
1734
	 * Are datagrid filters rendered separately?
1735
	 * @return boolean
1736
	 */
1737
	public function hasOuterFilterRendering()
1738
	{
1739
		return $this->outer_filter_rendering;
1740
	}
1741
1742
1743
	/**
1744
	 * Try to restore session stuff
1745
	 * @return void
1746
	 * @throws DataGridFilterNotFoundException
1747
	 */
1748
	public function findSessionValues()
1749
	{
1750
		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...
1751
			return;
1752
		}
1753
1754
		if (!$this->remember_state) {
1755
			return;
1756
		}
1757
1758
		if ($page = $this->getSessionData('_grid_page')) {
1759
			$this->page = $page;
1760
		}
1761
1762
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1763
			$this->per_page = $per_page;
1764
		}
1765
1766
		if ($sort = $this->getSessionData('_grid_sort')) {
1767
			$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...
1768
		}
1769
1770
		foreach ($this->getSessionData() as $key => $value) {
1771
			$other_session_keys = [
1772
				'_grid_per_page',
1773
				'_grid_sort',
1774
				'_grid_page',
1775
				'_grid_has_sorted',
1776
				'_grid_has_filtered',
1777
				'_grid_hidden_columns',
1778
				'_grid_hidden_columns_manipulated'
1779
			];
1780
1781
			if (!in_array($key, $other_session_keys)) {
1782
				try {
1783
					$this->getFilter($key);
1784
1785
					$this->filter[$key] = $value;
1786
1787
				} catch (DataGridException $e) {
1788
					if ($this->strict_session_filter_values) {
1789
						throw new DataGridFilterNotFoundException("Session filter: Filter [$key] not found");
1790
					}
1791
				}
1792
			}
1793
		}
1794
1795
		/**
1796
		 * When column is sorted via custom callback, apply it
1797
		 */
1798
		if (empty($this->sort_callback) && !empty($this->sort)) {
1799
			foreach ($this->sort as $key => $order) {
1800
				try {
1801
					$column = $this->getColumn($key);
1802
1803
				} catch (DataGridColumnNotFoundException $e) {
1804
					$this->deleteSessionData('_grid_sort');
1805
					$this->sort = [];
1806
1807
					return;
1808
				}
1809
1810
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1811
					$this->sort_callback = $column->getSortableCallback();
1812
				}
1813
			}
1814
		}
1815
	}
1816
1817
1818
	/********************************************************************************
1819
	 *                                    EXPORTS                                   *
1820
	 ********************************************************************************/
1821
1822
1823
	/**
1824
	 * Add export of type callback
1825
	 * @param string $text
1826
	 * @param callable $callback
1827
	 * @param boolean $filtered
1828
	 * @return Export\Export
1829
	 */
1830
	public function addExportCallback($text, $callback, $filtered = FALSE)
1831
	{
1832
		if (!is_callable($callback)) {
1833
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1834
		}
1835
1836
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1837
	}
1838
1839
1840
	/**
1841
	 * Add already implemented csv export
1842
	 * @param string $text
1843
	 * @param string $csv_file_name
1844
	 * @param string|null $output_encoding
1845
	 * @param string|null $delimiter
1846
	 * @param bool $include_bom
1847
	 * @return Export\Export
1848
	 */
1849 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...
1850
	{
1851
		return $this->addToExports(new Export\ExportCsv(
1852
			$this,
1853
			$text,
1854
			$csv_file_name,
1855
			FALSE,
1856
			$output_encoding,
1857
			$delimiter,
1858
			$include_bom
1859
		));
1860
	}
1861
1862
1863
	/**
1864
	 * Add already implemented csv export, but for filtered data
1865
	 * @param string $text
1866
	 * @param string $csv_file_name
1867
	 * @param string|null $output_encoding
1868
	 * @param string|null $delimiter
1869
	 * @param bool $include_bom
1870
	 * @return Export\Export
1871
	 */
1872 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...
1873
	{
1874
		return $this->addToExports(new Export\ExportCsv(
1875
			$this,
1876
			$text,
1877
			$csv_file_name,
1878
			TRUE,
1879
			$output_encoding,
1880
			$delimiter,
1881
			$include_bom
1882
		));
1883
	}
1884
1885
1886
	/**
1887
	 * Add export to array
1888
	 * @param Export\Export $export
1889
	 * @return Export\Export
1890
	 */
1891
	protected function addToExports(Export\Export $export)
1892
	{
1893
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1894
1895
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1896
1897
		return $this->exports[$id] = $export;
1898
	}
1899
1900
1901
	public function resetExportsLinks()
1902
	{
1903
		foreach ($this->exports as $id => $export) {
1904
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1905
		}
1906
	}
1907
1908
1909
	/********************************************************************************
1910
	 *                                TOOLBAR BUTTONS                               *
1911
	 ********************************************************************************/
1912
1913
1914
	/**
1915
	 * Add toolbar button
1916
	 * @param string $href
1917
	 * @param string $text
1918
	 * @param array  $params
1919
	 * @return ToolbarButton
1920
	 */
1921
	public function addToolbarButton($href, $text = '', $params = [])
1922
	{
1923
		$button = new ToolbarButton($this, $href, $text, $params);
1924
1925
		return $this->toolbar_buttons[] = $button;
1926
	}
1927
1928
1929
	/********************************************************************************
1930
	 *                                 GROUP ACTIONS                                *
1931
	 ********************************************************************************/
1932
1933
1934
	/**
1935
	 * Alias for add group select action
1936
	 * @param string $title
1937
	 * @param array  $options
1938
	 * @return GroupAction\GroupAction
1939
	 */
1940
	public function addGroupAction($title, $options = [])
1941
	{
1942
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1943
	}
1944
1945
1946
	/**
1947
	 * Add group action (select box)
1948
	 * @param string $title
1949
	 * @param array  $options
1950
	 * @return GroupAction\GroupAction
1951
	 */
1952
	public function addGroupSelectAction($title, $options = [])
1953
	{
1954
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1955
	}
1956
1957
1958
	/**
1959
	 * Add group action (text input)
1960
	 * @param string $title
1961
	 * @return GroupAction\GroupAction
1962
	 */
1963
	public function addGroupTextAction($title)
1964
	{
1965
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1966
	}
1967
1968
1969
	/**
1970
	 * Add group action (textarea)
1971
	 * @param string $title
1972
	 * @return GroupAction\GroupAction
1973
	 */
1974
	public function addGroupTextareaAction($title)
1975
	{
1976
		return $this->getGroupActionCollection()->addGroupTextareaAction($title);
1977
	}
1978
1979
1980
	/**
1981
	 * Get collection of all group actions
1982
	 * @return GroupAction\GroupActionCollection
1983
	 */
1984
	public function getGroupActionCollection()
1985
	{
1986
		if (!$this->group_action_collection) {
1987
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1988
		}
1989
1990
		return $this->group_action_collection;
1991
	}
1992
1993
1994
	/**
1995
	 * Has datagrid some group actions?
1996
	 * @return boolean
1997
	 */
1998
	public function hasGroupActions()
1999
	{
2000
		return (bool) $this->group_action_collection;
2001
	}
2002
2003
2004
	/********************************************************************************
2005
	 *                                   HANDLERS                                   *
2006
	 ********************************************************************************/
2007
2008
2009
	/**
2010
	 * Handler for changind page (just refresh site with page as persistent paramter set)
2011
	 * @param  int  $page
2012
	 * @return void
2013
	 */
2014
	public function handlePage($page)
2015
	{
2016
		/**
2017
		 * Session stuff
2018
		 */
2019
		$this->page = $page;
2020
		$this->saveSessionData('_grid_page', $page);
2021
2022
		$this->reload(['table']);
2023
	}
2024
2025
2026
	/**
2027
	 * Handler for sorting
2028
	 * @param array $sort
2029
	 * @return void
2030
	 * @throws DataGridColumnNotFoundException
2031
	 */
2032
	public function handleSort(array $sort)
2033
	{
2034
		foreach ($sort as $key => $value) {
2035
			try {
2036
				$column = $this->getColumn($key);
2037
2038
			} catch (DataGridColumnNotFoundException $e) {
2039
				unset($sort[$key]);
2040
				continue;
2041
			}
2042
2043
			if ($column->sortableResetPagination()) {
2044
				$this->saveSessionData('_grid_page', $this->page = 1);
2045
			}
2046
2047
			if ($column->getSortableCallback()) {
2048
				$this->sort_callback = $column->getSortableCallback();
2049
			}
2050
		}
2051
2052
		$this->saveSessionData('_grid_has_sorted', 1);
2053
		$this->saveSessionData('_grid_sort', $this->sort = $sort);
2054
		$this->reload(['table']);
2055
	}
2056
2057
2058
	/**
2059
	 * Handler for reseting the filter
2060
	 * @return void
2061
	 */
2062
	public function handleResetFilter()
2063
	{
2064
		/**
2065
		 * Session stuff
2066
		 */
2067
		$this->deleteSessionData('_grid_page');
2068
2069
		if ($this->default_filter_use_on_reset) {
2070
			$this->deleteSessionData('_grid_has_filtered');
2071
		}
2072
2073
		if ($this->default_sort_use_on_reset) {
2074
			$this->deleteSessionData('_grid_has_sorted');
2075
		}
2076
2077
		foreach ($this->getSessionData() as $key => $value) {
2078
			if (!in_array($key, [
2079
				'_grid_per_page',
2080
				'_grid_sort',
2081
				'_grid_page',
2082
				'_grid_has_filtered',
2083
				'_grid_has_sorted',
2084
				'_grid_hidden_columns',
2085
				'_grid_hidden_columns_manipulated'
2086
				])) {
2087
2088
				$this->deleteSessionData($key);
2089
			}
2090
		}
2091
2092
		$this->filter = [];
2093
2094
		$this->reload(['grid']);
2095
	}
2096
2097
2098
	/**
2099
	 * @param  string $key
2100
	 * @return void
2101
	 */
2102
	public function handleResetColumnFilter($key)
2103
	{
2104
		$this->deleteSessionData($key);
2105
		unset($this->filter[$key]);
2106
2107
		$this->reload(['grid']);
2108
	}
2109
2110
2111
	/**
2112
	 * @param bool $reset
2113
	 * @return static
2114
	 */
2115
	public function setColumnReset($reset = TRUE)
2116
	{
2117
		$this->has_column_reset = (bool) $reset;
2118
2119
		return $this;
2120
	}
2121
2122
2123
	/**
2124
	 * @return bool
2125
	 */
2126
	public function hasColumnReset()
2127
	{
2128
		return $this->has_column_reset;
2129
	}
2130
2131
2132
	/**
2133
	 * @param  Filter\Filter[] $filters
2134
	 * @return void
2135
	 */
2136
	public function sendNonEmptyFiltersInPayload($filters)
2137
	{
2138
		if (!$this->hasColumnReset()) {
2139
			return;
2140
		}
2141
2142
		$non_empty_filters = [];
2143
2144
		foreach ($filters as $filter) {
2145
			if ($filter->isValueSet()) {
2146
				$non_empty_filters[] = $filter->getKey();
2147
			}
2148
		}
2149
2150
		$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...
2151
	}
2152
2153
2154
	/**
2155
	 * Handler for export
2156
	 * @param  int $id Key for particular export class in array $this->exports
2157
	 * @return void
2158
	 */
2159
	public function handleExport($id)
2160
	{
2161
		if (!isset($this->exports[$id])) {
2162
			throw new Nette\Application\ForbiddenRequestException;
2163
		}
2164
2165
		if (!empty($this->columns_export_order)) {
2166
			$this->setColumnsOrder($this->columns_export_order);
2167
		}
2168
2169
		$export = $this->exports[$id];
2170
2171
		/**
2172
		 * Invoke possible events
2173
		 */
2174
		$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...
2175
2176
		if ($export->isFiltered()) {
2177
			$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...
2178
			$filter    = $this->assableFilters();
2179
		} else {
2180
			$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...
2181
			$filter    = [];
2182
		}
2183
2184
		if (NULL === $this->dataModel) {
2185
			throw new DataGridException('You have to set a data source first.');
2186
		}
2187
2188
		$rows = [];
2189
2190
		$items = Nette\Utils\Callback::invokeArgs(
2191
			[$this->dataModel, 'filterData'], [
2192
				NULL,
2193
				$this->createSorting($this->sort, $this->sort_callback),
2194
				$filter
2195
			]
2196
		);
2197
2198
		foreach ($items as $item) {
2199
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
2200
		}
2201
2202
		if ($export instanceof Export\ExportCsv) {
2203
			$export->invoke($rows);
2204
		} else {
2205
			$export->invoke($items);
2206
		}
2207
2208
		if ($export->isAjax()) {
2209
			$this->reload();
2210
		}
2211
	}
2212
2213
2214
	/**
2215
	 * Handler for getting children of parent item (e.g. category)
2216
	 * @param  int $parent
2217
	 * @return void
2218
	 */
2219
	public function handleGetChildren($parent)
2220
	{
2221
		$this->setDataSource(
2222
			call_user_func($this->tree_view_children_callback, $parent)
2223
		);
2224
2225
		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...
2226
			$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...
2227
			$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...
2228
2229
			$this->redrawControl('items');
2230
2231
			$this->onRedraw();
2232
		} else {
2233
			$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...
2234
		}
2235
	}
2236
2237
2238
	/**
2239
	 * Handler for getting item detail
2240
	 * @param  mixed $id
2241
	 * @return void
2242
	 */
2243
	public function handleGetItemDetail($id)
2244
	{
2245
		$this->getTemplate()->add('toggle_detail', $id);
2246
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
2247
2248
		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...
2249
			$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...
2250
			$this->redrawControl('items');
2251
2252
			/**
2253
			 * Only for nette 2.4
2254
			 */
2255
			if (method_exists($this->getTemplate()->getLatte(), 'addProvider')) {
2256
				$this->redrawControl('gridSnippets');
2257
			}
2258
2259
			$this->onRedraw();
2260
		} else {
2261
			$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...
2262
		}
2263
	}
2264
2265
2266
	/**
2267
	 * Handler for inline editing
2268
	 * @param  mixed $id
2269
	 * @param  mixed $key
2270
	 * @return void
2271
	 */
2272
	public function handleEdit($id, $key)
2273
	{
2274
		$column = $this->getColumn($key);
2275
		$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...
2276
2277
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
2278
	}
2279
2280
2281
	/**
2282
	 * Redraw $this
2283
	 * @return void
2284
	 */
2285
	public function reload($snippets = [])
2286
	{
2287
		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...
2288
			$this->redrawControl('tbody');
2289
			$this->redrawControl('pagination');
2290
2291
			/**
2292
			 * manualy reset exports links...
2293
			 */
2294
			$this->resetExportsLinks();
2295
			$this->redrawControl('exports');
2296
2297
			foreach ($snippets as $snippet) {
2298
				$this->redrawControl($snippet);
2299
			}
2300
2301
			$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...
2302
			$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...
2303
2304
			$this->onRedraw();
2305
		} else {
2306
			$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...
2307
		}
2308
	}
2309
2310
2311
	/**
2312
	 * Handler for column status
2313
	 * @param  string $id
2314
	 * @param  string $key
2315
	 * @param  string $value
2316
	 * @return void
2317
	 */
2318
	public function handleChangeStatus($id, $key, $value)
2319
	{
2320
		if (empty($this->columns[$key])) {
2321
			throw new DataGridException("ColumnStatus[$key] does not exist");
2322
		}
2323
2324
		$this->columns[$key]->onChange($id, $value);
2325
	}
2326
2327
2328
	/**
2329
	 * Redraw just one row via ajax
2330
	 * @param  int   $id
2331
	 * @param  mixed $primary_where_column
2332
	 * @return void
2333
	 */
2334
	public function redrawItem($id, $primary_where_column = NULL)
2335
	{
2336
		$this->snippets_set = TRUE;
2337
2338
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
2339
2340
		$this->redrawControl('items');
2341
2342
		$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...
2343
2344
		$this->onRedraw();
2345
	}
2346
2347
2348
	/**
2349
	 * Tell datagrid to display all columns
2350
	 * @return void
2351
	 */
2352
	public function handleShowAllColumns()
2353
	{
2354
		$this->deleteSessionData('_grid_hidden_columns');
2355
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2356
2357
		$this->redrawControl();
2358
2359
		$this->onRedraw();
2360
	}
2361
2362
2363
	/**
2364
	 * Tell datagrid to display default columns
2365
	 * @return void
2366
	 */
2367
	public function handleShowDefaultColumns()
2368
	{
2369
		$this->deleteSessionData('_grid_hidden_columns');
2370
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
2371
2372
		$this->redrawControl();
2373
2374
		$this->onRedraw();
2375
	}
2376
2377
2378
	/**
2379
	 * Reveal particular column
2380
	 * @param  string $column
2381
	 * @return void
2382
	 */
2383 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...
2384
	{
2385
		$columns = $this->getSessionData('_grid_hidden_columns');
2386
2387
		if (!empty($columns)) {
2388
			$pos = array_search($column, $columns);
2389
2390
			if ($pos !== FALSE) {
2391
				unset($columns[$pos]);
2392
			}
2393
		}
2394
2395
		$this->saveSessionData('_grid_hidden_columns', $columns);
2396
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2397
2398
		$this->redrawControl();
2399
2400
		$this->onRedraw();
2401
	}
2402
2403
2404
	/**
2405
	 * Notice datagrid to not display particular columns
2406
	 * @param  string $column
2407
	 * @return void
2408
	 */
2409 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...
2410
	{
2411
		/**
2412
		 * Store info about hiding a column to session
2413
		 */
2414
		$columns = $this->getSessionData('_grid_hidden_columns');
2415
2416
		if (empty($columns)) {
2417
			$columns = [$column];
2418
		} else if (!in_array($column, $columns)) {
2419
			array_push($columns, $column);
2420
		}
2421
2422
		$this->saveSessionData('_grid_hidden_columns', $columns);
2423
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2424
2425
		$this->redrawControl();
2426
2427
		$this->onRedraw();
2428
	}
2429
2430
2431
	public function handleActionCallback($__key, $__id)
2432
	{
2433
		$action = $this->getAction($__key);
2434
2435
		if (!($action instanceof Column\ActionCallback)) {
2436
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2437
		}
2438
2439
		$action->onClick($__id);
2440
	}
2441
2442
2443
	/********************************************************************************
2444
	 *                                  PAGINATION                                  *
2445
	 ********************************************************************************/
2446
2447
2448
	/**
2449
	 * Set options of select "items_per_page"
2450
	 * @param array $items_per_page_list
2451
	 * @return static
2452
	 */
2453
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
2454
	{
2455
		$this->items_per_page_list = $items_per_page_list;
2456
2457
		if ($include_all) {
2458
			$this->items_per_page_list[] = 'all';
2459
		}
2460
2461
		return $this;
2462
	}
2463
2464
2465
	/**
2466
	 * Set default "items per page" value in pagination select
2467
	 * @param $count
2468
	 * @return static
2469
	 */
2470
	public function setDefaultPerPage($count)
2471
	{
2472
		$this->default_per_page = $count;
2473
2474
		return $this;
2475
	}
2476
2477
2478
	/**
2479
	 * User may set default "items per page" value, apply it
2480
	 * @return void
2481
	 */
2482
	public function findDefaultPerPage()
2483
	{
2484
		if (!empty($this->per_page)) {
2485
			return;
2486
		}
2487
2488
		if (!empty($this->default_per_page)) {
2489
			$this->per_page = $this->default_per_page;
2490
		}
2491
2492
		$this->saveSessionData('_grid_per_page', $this->per_page);
2493
	}
2494
2495
2496
	/**
2497
	 * Paginator factory
2498
	 * @return Components\DataGridPaginator\DataGridPaginator
2499
	 */
2500
	public function createComponentPaginator()
2501
	{
2502
		/**
2503
		 * Init paginator
2504
		 */
2505
		$component = new Components\DataGridPaginator\DataGridPaginator(
2506
			$this->getTranslator(),
2507
			static::$icon_prefix
2508
		);
2509
		$paginator = $component->getPaginator();
2510
2511
		$paginator->setPage($this->page);
2512
		$paginator->setItemsPerPage($this->getPerPage());
2513
2514
		return $component;
2515
	}
2516
2517
2518
	/**
2519
	 * Get parameter per_page
2520
	 * @return int
2521
	 */
2522
	public function getPerPage()
2523
	{
2524
		$items_per_page_list = $this->getItemsPerPageList();
2525
2526
		$per_page = $this->per_page ?: reset($items_per_page_list);
2527
2528
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2529
			$per_page = reset($items_per_page_list);
2530
		}
2531
2532
		return $per_page;
2533
	}
2534
2535
2536
	/**
2537
	 * Get associative array of items_per_page_list
2538
	 * @return array
2539
	 */
2540
	public function getItemsPerPageList()
2541
	{
2542
		if (empty($this->items_per_page_list)) {
2543
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2544
		}
2545
2546
		$list = array_flip($this->items_per_page_list);
2547
2548
		foreach ($list as $key => $value) {
2549
			$list[$key] = $key;
2550
		}
2551
2552
		if (array_key_exists('all', $list)) {
2553
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2554
		}
2555
2556
		return $list;
2557
	}
2558
2559
2560
	/**
2561
	 * Order Grid to "be paginated"
2562
	 * @param bool $do
2563
	 * @return static
2564
	 */
2565
	public function setPagination($do)
2566
	{
2567
		$this->do_paginate = (bool) $do;
2568
2569
		return $this;
2570
	}
2571
2572
2573
	/**
2574
	 * Tell whether Grid is paginated
2575
	 * @return bool
2576
	 */
2577
	public function isPaginated()
2578
	{
2579
		return $this->do_paginate;
2580
	}
2581
2582
2583
	/**
2584
	 * Return current paginator class
2585
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2586
	 */
2587
	public function getPaginator()
2588
	{
2589
		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...
2590
			return $this['paginator'];
2591
		}
2592
2593
		return NULL;
2594
	}
2595
2596
2597
	/********************************************************************************
2598
	 *                                     I18N                                     *
2599
	 ********************************************************************************/
2600
2601
2602
	/**
2603
	 * Set datagrid translator
2604
	 * @param Nette\Localization\ITranslator $translator
2605
	 * @return static
2606
	 */
2607
	public function setTranslator(Nette\Localization\ITranslator $translator)
2608
	{
2609
		$this->translator = $translator;
2610
2611
		return $this;
2612
	}
2613
2614
2615
	/**
2616
	 * Get translator for datagrid
2617
	 * @return Nette\Localization\ITranslator
2618
	 */
2619
	public function getTranslator()
2620
	{
2621
		if (!$this->translator) {
2622
			$this->translator = new Localization\SimpleTranslator;
2623
		}
2624
2625
		return $this->translator;
2626
	}
2627
2628
2629
	/********************************************************************************
2630
	 *                                 COLUMNS ORDER                                *
2631
	 ********************************************************************************/
2632
2633
2634
	/**
2635
	 * Set order of datagrid columns
2636
	 * @param array $order
2637
	 * @return static
2638
	 */
2639
	public function setColumnsOrder($order)
2640
	{
2641
		$new_order = [];
2642
2643
		foreach ($order as $key) {
2644
			if (isset($this->columns[$key])) {
2645
				$new_order[$key] = $this->columns[$key];
2646
			}
2647
		}
2648
2649
		if (sizeof($new_order) === sizeof($this->columns)) {
2650
			$this->columns = $new_order;
2651
		} else {
2652
			throw new DataGridException('When changing columns order, you have to specify all columns');
2653
		}
2654
2655
		return $this;
2656
	}
2657
2658
2659
	/**
2660
	 * Columns order may be different for export and normal grid
2661
	 * @param array $order
2662
	 */
2663
	public function setColumnsExportOrder($order)
2664
	{
2665
		$this->columns_export_order = (array) $order;
2666
	}
2667
2668
2669
	/********************************************************************************
2670
	 *                                SESSION & URL                                 *
2671
	 ********************************************************************************/
2672
2673
2674
	/**
2675
	 * Find some unique session key name
2676
	 * @return string
2677
	 */
2678
	public function getSessionSectionName()
2679
	{
2680
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2681
	}
2682
2683
2684
	/**
2685
	 * Should datagrid remember its filters/pagination/etc using session?
2686
	 * @param bool $remember
2687
	 * @return static
2688
	 */
2689
	public function setRememberState($remember = TRUE)
2690
	{
2691
		$this->remember_state = (bool) $remember;
2692
2693
		return $this;
2694
	}
2695
2696
2697
	/**
2698
	 * Should datagrid refresh url using history API?
2699
	 * @param bool $refresh
2700
	 * @return static
2701
	 */
2702
	public function setRefreshUrl($refresh = TRUE)
2703
	{
2704
		$this->refresh_url = (bool) $refresh;
2705
2706
2707
		return $this;
2708
	}
2709
2710
2711
	/**
2712
	 * Get session data if functionality is enabled
2713
	 * @param  string $key
2714
	 * @return mixed
2715
	 */
2716
	public function getSessionData($key = NULL, $default_value = NULL)
2717
	{
2718
		if (!$this->remember_state) {
2719
			return $key ? $default_value : [];
2720
		}
2721
2722
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2723
	}
2724
2725
2726
	/**
2727
	 * Save session data - just if it is enabled
2728
	 * @param  string $key
2729
	 * @param  mixed  $value
2730
	 * @return void
2731
	 */
2732
	public function saveSessionData($key, $value)
2733
	{
2734
		if ($this->remember_state) {
2735
			$this->grid_session->{$key} = $value;
2736
		}
2737
	}
2738
2739
2740
	/**
2741
	 * Delete session data
2742
	 * @return void
2743
	 */
2744
	public function deleteSessionData($key)
2745
	{
2746
		unset($this->grid_session->{$key});
2747
	}
2748
2749
2750
	/**
2751
	 * Delete session data
2752
	 * @return void
2753
	 * @deprecated
2754
	 */
2755
	public function deleteSesssionData($key)
2756
	{
2757
		@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...
2758
		return $this->deleteSessionData($key);
2759
	}
2760
2761
2762
	/********************************************************************************
2763
	 *                                  ITEM DETAIL                                 *
2764
	 ********************************************************************************/
2765
2766
2767
	/**
2768
	 * Get items detail parameters
2769
	 * @return array
2770
	 */
2771
	public function getItemsDetail()
2772
	{
2773
		return $this->items_detail;
2774
	}
2775
2776
2777
	/**
2778
	 * Items can have thair detail - toggled
2779
	 * @param mixed $detail callable|string|bool
2780
	 * @param bool|NULL $primary_where_column
2781
	 * @return Column\ItemDetail
2782
	 */
2783
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2784
	{
2785
		if ($this->isSortable()) {
2786
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2787
		}
2788
2789
		$this->items_detail = new Column\ItemDetail(
2790
			$this,
2791
			$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...
2792
		);
2793
2794
		if (is_string($detail)) {
2795
			/**
2796
			 * Item detail will be in separate template
2797
			 */
2798
			$this->items_detail->setType('template');
2799
			$this->items_detail->setTemplate($detail);
2800
2801
		} else if (is_callable($detail)) {
2802
			/**
2803
			 * Item detail will be rendered via custom callback renderer
2804
			 */
2805
			$this->items_detail->setType('renderer');
2806
			$this->items_detail->setRenderer($detail);
2807
2808
		} else if (TRUE === $detail) {
2809
			/**
2810
			 * Item detail will be rendered probably via block #detail
2811
			 */
2812
			$this->items_detail->setType('block');
2813
2814
		} else {
2815
			throw new DataGridException(
2816
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2817
			);
2818
		}
2819
2820
		return $this->items_detail;
2821
	}
2822
2823
2824
	/**
2825
	 * @param callable $callable_set_container 
2826
	 * @return static
2827
	 */
2828
	public function setItemsDetailForm(callable $callable_set_container)
2829
	{
2830
		if ($this->items_detail instanceof Column\ItemDetail) {
2831
			$this->items_detail->setForm(
2832
				new Utils\ItemDetailForm($callable_set_container)
2833
			);
2834
2835
			return $this;
2836
		}
2837
2838
		throw new DataGridException('Please set the ItemDetail first.');
2839
	}
2840
2841
2842
	/**
2843
	 * @return Nette\Forms\Container|NULL
2844
	 */
2845
	public function getItemDetailForm()
2846
	{
2847
		if ($this->items_detail instanceof Column\ItemDetail) {
2848
			return $this->items_detail->getForm();
2849
		}
2850
2851
		return NULL;
2852
	}
2853
2854
2855
	/********************************************************************************
2856
	 *                                ROW PRIVILEGES                                *
2857
	 ********************************************************************************/
2858
2859
2860
	/**
2861
	 * @param  callable $condition
2862
	 * @return void
2863
	 */
2864
	public function allowRowsGroupAction(callable $condition)
2865
	{
2866
		$this->row_conditions['group_action'] = $condition;
2867
	}
2868
2869
2870
	/**
2871
	 * @param  callable $condition
2872
	 * @return void
2873
	 */
2874
	public function allowRowsInlineEdit(callable $condition)
2875
	{
2876
		$this->row_conditions['inline_edit'] = $condition;
2877
	}
2878
2879
2880
	/**
2881
	 * @param  string   $key
2882
	 * @param  callable $condition
2883
	 * @return void
2884
	 */
2885
	public function allowRowsAction($key, callable $condition)
2886
	{
2887
		$this->row_conditions['action'][$key] = $condition;
2888
	}
2889
2890
2891
	/**
2892
	 * @param  string      $name
2893
	 * @param  string|null $key
2894
	 * @return bool|callable
2895
	 */
2896
	public function getRowCondition($name, $key = NULL)
2897
	{
2898
		if (!isset($this->row_conditions[$name])) {
2899
			return FALSE;
2900
		}
2901
2902
		$condition = $this->row_conditions[$name];
2903
2904
		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...
2905
			return $condition;
2906
		}
2907
2908
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2909
	}
2910
2911
2912
	/********************************************************************************
2913
	 *                               COLUMN CALLBACK                                *
2914
	 ********************************************************************************/
2915
2916
2917
	/**
2918
	 * @param  string   $key
2919
	 * @param  callable $callback
2920
	 * @return void
2921
	 */
2922
	public function addColumnCallback($key, callable $callback)
2923
	{
2924
		$this->column_callbacks[$key] = $callback;
2925
	}
2926
2927
2928
	/**
2929
	 * @param  string $key
2930
	 * @return callable|null
2931
	 */
2932
	public function getColumnCallback($key)
2933
	{
2934
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2935
	}
2936
2937
2938
	/********************************************************************************
2939
	 *                                 INLINE EDIT                                  *
2940
	 ********************************************************************************/
2941
2942
2943
	/**
2944
	 * @return InlineEdit
2945
	 */
2946
	public function addInlineEdit($primary_where_column = NULL)
2947
	{
2948
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2949
2950
		return $this->inlineEdit;
2951
	}
2952
2953
2954
	/**
2955
	 * @return InlineEdit|null
2956
	 */
2957
	public function getInlineEdit()
2958
	{
2959
		return $this->inlineEdit;
2960
	}
2961
2962
2963
	/**
2964
	 * @param  mixed $id
2965
	 * @return void
2966
	 */
2967
	public function handleInlineEdit($id)
2968
	{
2969
		if ($this->inlineEdit) {
2970
			$this->inlineEdit->setItemId($id);
2971
2972
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2973
2974
			$this['filter']['inline_edit']->addHidden('_id', $id);
2975
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2976
2977
			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...
2978
				$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...
2979
			}
2980
2981
			$this->redrawItem($id, $primary_where_column);
2982
		}
2983
	}
2984
2985
2986
	/********************************************************************************
2987
	 *                                  INLINE ADD                                  *
2988
	 ********************************************************************************/
2989
2990
2991
	/**
2992
	 * @return InlineEdit
2993
	 */
2994
	public function addInlineAdd()
2995
	{
2996
		$this->inlineAdd = new InlineEdit($this);
2997
2998
		$this->inlineAdd
2999
			->setTitle('ublaboo_datagrid.add')
3000
			->setIcon('plus')
3001
			->setClass('btn btn-xs btn-default');
3002
3003
		return $this->inlineAdd;
3004
	}
3005
3006
3007
	/**
3008
	 * @return InlineEdit|null
3009
	 */
3010
	public function getInlineAdd()
3011
	{
3012
		return $this->inlineAdd;
3013
	}
3014
3015
3016
	/********************************************************************************
3017
	 *                               HIDEABLE COLUMNS                               *
3018
	 ********************************************************************************/
3019
3020
3021
	/**
3022
	 * Can datagrid hide colums?
3023
	 * @return boolean
3024
	 */
3025
	public function canHideColumns()
3026
	{
3027
		return (bool) $this->can_hide_columns;
3028
	}
3029
3030
3031
	/**
3032
	 * Order Grid to set columns hideable.
3033
	 * @return static
3034
	 */
3035
	public function setColumnsHideable()
3036
	{
3037
		$this->can_hide_columns = TRUE;
3038
3039
		return $this;
3040
	}
3041
3042
3043
	/********************************************************************************
3044
	 *                                COLUMNS SUMMARY                               *
3045
	 ********************************************************************************/
3046
3047
3048
	/**
3049
	 * Will datagrid show summary in the end?
3050
	 * @return bool
3051
	 */
3052
	public function hasColumnsSummary()
3053
	{
3054
		return $this->columnsSummary instanceof ColumnsSummary;
3055
	}
3056
3057
3058
	/**
3059
	 * Set columns to be summarized in the end.
3060
	 * @param array    $columns
3061
	 * @param callable $rowCallback
3062
	 * @return \Ublaboo\DataGrid\ColumnsSummary
3063
	 */
3064
	public function setColumnsSummary(array $columns, $rowCallback = NULL)
3065
	{
3066
		if (!empty($rowCallback)) {
3067
			if (!is_callable($rowCallback)) {
3068
				throw new \InvalidArgumentException('Row summary callback must be callable');
3069
			}
3070
		}
3071
3072
		$this->columnsSummary = new ColumnsSummary($this, $columns, $rowCallback);
3073
3074
		return $this->columnsSummary;
3075
	}
3076
3077
3078
	/**
3079
	 * @return ColumnsSummary|NULL
3080
	 */
3081
	public function getColumnsSummary()
3082
	{
3083
		return $this->columnsSummary;
3084
	}
3085
3086
3087
	/********************************************************************************
3088
	 *                                   INTERNAL                                   *
3089
	 ********************************************************************************/
3090
3091
3092
	/**
3093
	 * Tell grid filters to by submitted automatically
3094
	 * @param bool $auto
3095
	 */
3096
	public function setAutoSubmit($auto = TRUE)
3097
	{
3098
		$this->auto_submit = (bool) $auto;
3099
3100
		return $this;
3101
	}
3102
3103
3104
	/**
3105
	 * @return bool
3106
	 */
3107
	public function hasAutoSubmit()
3108
	{
3109
		return $this->auto_submit;
3110
	}
3111
3112
3113
	/**
3114
	 * Submit button when no auto-submitting is used
3115
	 * @return Filter\SubmitButton
3116
	 */
3117
	public function getFilterSubmitButton()
3118
	{
3119
		if ($this->hasAutoSubmit()) {
3120
			throw new DataGridException(
3121
				'DataGrid has auto-submit. Turn it off before setting filter submit button.'
3122
			);
3123
		}
3124
3125
		if ($this->filter_submit_button === NULL) {
3126
			$this->filter_submit_button = new Filter\SubmitButton($this);
3127
		}
3128
3129
		return $this->filter_submit_button;
3130
	}
3131
3132
3133
	/********************************************************************************
3134
	 *                                   INTERNAL                                   *
3135
	 ********************************************************************************/
3136
3137
3138
	/**
3139
	 * Get count of columns
3140
	 * @return int
3141
	 */
3142
	public function getColumnsCount()
3143
	{
3144
		$count = sizeof($this->getColumns());
3145
3146
		if (!empty($this->actions)
3147
			|| $this->isSortable()
3148
			|| $this->getItemsDetail()
3149
			|| $this->getInlineEdit()
3150
			|| $this->getInlineAdd()) {
3151
			$count++;
3152
		}
3153
3154
		if ($this->hasGroupActions()) {
3155
			$count++;
3156
		}
3157
3158
		return $count;
3159
	}
3160
3161
3162
	/**
3163
	 * Get primary key of datagrid data source
3164
	 * @return string
3165
	 */
3166
	public function getPrimaryKey()
3167
	{
3168
		return $this->primary_key;
3169
	}
3170
3171
3172
	/**
3173
	 * Get set of set columns
3174
	 * @return Column\IColumn[]
3175
	 */
3176
	public function getColumns()
3177
	{
3178
		$return = $this->columns;
3179
3180
		try {
3181
			$this->getParent();
3182
3183
			if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
3184
				$columns_to_hide = [];
3185
3186
				foreach ($this->columns as $key => $column) {
3187
					if ($column->getDefaultHide()) {
3188
						$columns_to_hide[] = $key;
3189
					}
3190
				}
3191
3192
				if (!empty($columns_to_hide)) {
3193
					$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
3194
					$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
3195
				}
3196
			}
3197
3198
			$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
3199
3200
			foreach ($hidden_columns as $column) {
3201
				if (!empty($this->columns[$column])) {
3202
					$this->columns_visibility[$column] = [
3203
						'visible' => FALSE
3204
					];
3205
3206
					unset($return[$column]);
3207
				}
3208
			}
3209
3210
		} catch (DataGridHasToBeAttachedToPresenterComponentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
3211
		}
3212
3213
		return $return;
3214
	}
3215
3216
3217
	public function getColumnsVisibility()
3218
	{
3219
		$return = $this->columns_visibility;
3220
3221
		foreach ($this->columns_visibility as $key => $column) {
3222
			$return[$key]['column'] = $this->columns[$key];
3223
		}
3224
3225
		return $return;
3226
	}
3227
3228
3229
	/**
3230
	 * @return PresenterComponent
3231
	 */
3232
	public function getParent()
3233
	{
3234
		$parent = parent::getParent();
3235
3236
		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...
3237
			throw new DataGridHasToBeAttachedToPresenterComponentException(
3238
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
3239
			);
3240
		}
3241
3242
		return $parent;
3243
	}
3244
3245
3246
	/**
3247
	 * @return strign
3248
	 */
3249
	public function getSortableParentPath()
3250
	{
3251
		return $this->getParent()->lookupPath(Nette\Application\UI\Control::class, FALSE);
3252
	}
3253
3254
3255
	/**
3256
	 * Some of datagrid columns is hidden by default
3257
	 * @param bool $default_hide
3258
	 */
3259
	public function setSomeColumnDefaultHide($default_hide)
3260
	{
3261
		$this->some_column_default_hide = $default_hide;
3262
	}
3263
3264
3265
	/**
3266
	 * Are some of columns hidden bydefault?
3267
	 */
3268
	public function hasSomeColumnDefaultHide()
3269
	{
3270
		return $this->some_column_default_hide;
3271
	}
3272
3273
3274
	/**
3275
	 * Simply refresh url
3276
	 * @return void
3277
	 */
3278
	public function handleRefreshState()
3279
	{
3280
		$this->findSessionValues();
3281
		$this->findDefaultFilter();
3282
		$this->findDefaultSort();
3283
		$this->findDefaultPerPage();
3284
3285
		$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...
3286
		$this->redrawControl('non-existing-snippet');
3287
	}
3288
3289
}
3290