Completed
Push — master ( f960a5...80ccee )
by Pavel
03:21
created

DataGrid   F

Complexity

Total Complexity 347

Size/Duplication

Total Lines 3011
Duplicated Lines 5.25 %

Coupling/Cohesion

Components 5
Dependencies 46

Importance

Changes 47
Bugs 10 Features 12
Metric Value
wmc 347
c 47
b 10
f 12
lcom 5
cbo 46
dl 158
loc 3011
rs 0.5217

133 Methods

Rating   Name   Duplication   Size   Complexity  
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
C createComponentFilter() 17 86 8
B __construct() 0 26 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 setTemplateFile() 0 6 1
A getTemplateFile() 0 4 2
A getOriginalTemplateFile() 0 4 1
A useHappyComponents() 0 8 2
A setDefaultSort() 0 12 2
A findDefaultSort() 0 12 3
A setSortable() 0 10 2
A setSortableHandler() 0 6 1
A isSortable() 0 4 1
A getSortableHandler() 0 4 1
A createSorting() 0 9 2
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 4 1
A addColumnCheck() 0 6 2
A addAction() 0 11 3
A addActionCallback() 0 17 3
A getAction() 0 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 28 10
A removeFilter() 0 4 1
A getFilter() 0 8 2
A setStrictSessionFilterValues() 0 4 1
D setFilterContainerDefaults() 18 27 10
D filterSucceeded() 0 104 25
A setOuterFilterRendering() 0 6 1
A hasOuterFilterRendering() 0 4 1
D findSessionValues() 0 67 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 39 5
B handleResetFilter() 0 29 4
C handleExport() 0 48 8
A handleGetChildren() 0 17 2
A handleGetItemDetail() 0 14 2
A handleEdit() 0 7 1
A reload() 0 23 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 deleteSesssionData() 0 4 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 10 1
A getInlineAdd() 0 4 1
A canHideColumns() 0 4 1
A setColumnsHideable() 0 6 1
A hasColumnsSummary() 0 4 1
A setColumnsSummary() 0 6 1
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 33 7
A getColumnsVisibility() 0 10 2
A getParent() 0 12 2
A setSomeColumnDefaultHide() 0 4 1
A hasSomeColumnDefaultHide() 0 4 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\DataGridHasToBeAttachedToPresenterComponentException;
18
use Ublaboo\DataGrid\Utils\Sorting;
19
use Ublaboo\DataGrid\InlineEdit\InlineEdit;
20
use Ublaboo\DataGrid\ColumnsSummary;
21
use Ublaboo\DataGrid\Toolbar\ToolbarButton;
22
23
/**
24
 * @method onRedraw()
25
 * @method onRender()
26
 * @method onColumnAdd()
27
 */
28
class DataGrid extends Nette\Application\UI\Control
29
{
30
31
	/**
32
	 * @var callable[]
33
	 */
34
	public $onRedraw;
35
36
	/**
37
	 * @var callable[]
38
	 */
39
	public $onRender = [];
40
41
	/**
42
	 * @var callable[]
43
	 */
44
	public $onColumnAdd;
45
46
	/**
47
	 * @var string
48
	 */
49
	public static $icon_prefix = 'fa fa-';
50
51
	/**
52
	 * Default form method
53
	 * @var string
54
	 */
55
	public static $form_method = 'post';
56
57
	/**
58
	 * When set to TRUE, datagrid throws an exception
59
	 * 	when tring to get related entity within join and entity does not exist
60
	 * @var bool
61
	 */
62
	public $strict_entity_property = FALSE;
63
64
	/**
65
	 * When set to TRUE, datagrid throws an exception
66
	 * 	when tring to set filter value, that does not exist (select, multiselect, etc)
67
	 * @var bool
68
	 */
69
	public $strict_session_filter_values = TRUE;
70
71
	/**
72
	 * @var int
73
	 * @persistent
74
	 */
75
	public $page = 1;
76
77
	/**
78
	 * @var int|string
79
	 * @persistent
80
	 */
81
	public $per_page;
82
83
	/**
84
	 * @var array
85
	 * @persistent
86
	 */
87
	public $sort = [];
88
89
	/**
90
	 * @var array
91
	 */
92
	public $default_sort = [];
93
94
	/**
95
	 * @var array
96
	 */
97
	public $default_filter = [];
98
99
	/**
100
	 * @var bool
101
	 */
102
	public $default_filter_use_on_reset = TRUE;
103
104
	/**
105
	 * @var array
106
	 * @persistent
107
	 */
108
	public $filter = [];
109
110
	/**
111
	 * @var callable|null
112
	 */
113
	protected $sort_callback = NULL;
114
115
	/**
116
	 * @var bool
117
	 */
118
	protected $use_happy_components = TRUE;
119
120
	/**
121
	 * @var callable
122
	 */
123
	protected $rowCallback;
124
125
	/**
126
	 * @var array
127
	 */
128
	protected $items_per_page_list;
129
130
	/**
131
	 * @var int
132
	 */
133
	protected $default_per_page;
134
135
	/**
136
	 * @var string
137
	 */
138
	protected $template_file;
139
140
	/**
141
	 * @var Column\IColumn[]
142
	 */
143
	protected $columns = [];
144
145
	/**
146
	 * @var Column\Action[]
147
	 */
148
	protected $actions = [];
149
150
	/**
151
	 * @var GroupAction\GroupActionCollection
152
	 */
153
	protected $group_action_collection;
154
155
	/**
156
	 * @var Filter\Filter[]
157
	 */
158
	protected $filters = [];
159
160
	/**
161
	 * @var Export\Export[]
162
	 */
163
	protected $exports = [];
164
165
	/**
166
	 * @var ToolbarButton[]
167
	 */
168
	protected $toolbar_buttons = [];
169
170
	/**
171
	 * @var DataModel
172
	 */
173
	protected $dataModel;
174
175
	/**
176
	 * @var DataFilter
177
	 */
178
	protected $dataFilter;
179
180
	/**
181
	 * @var string
182
	 */
183
	protected $primary_key = 'id';
184
185
	/**
186
	 * @var bool
187
	 */
188
	protected $do_paginate = TRUE;
189
190
	/**
191
	 * @var bool
192
	 */
193
	protected $csv_export = TRUE;
194
195
	/**
196
	 * @var bool
197
	 */
198
	protected $csv_export_filtered = TRUE;
199
200
	/**
201
	 * @var bool
202
	 */
203
	protected $sortable = FALSE;
204
205
	/**
206
	 * @var string
207
	 */
208
	protected $sortable_handler = 'sort!';
209
210
	/**
211
	 * @var string
212
	 */
213
	protected $original_template;
214
215
	/**
216
	 * @var array
217
	 */
218
	protected $redraw_item;
219
220
	/**
221
	 * @var mixed
222
	 */
223
	protected $translator;
224
225
	/**
226
	 * @var bool
227
	 */
228
	protected $force_filter_active;
229
230
	/**
231
	 * @var callable
232
	 */
233
	protected $tree_view_children_callback;
234
235
	/**
236
	 * @var callable
237
	 */
238
	protected $tree_view_has_children_callback;
239
240
	/**
241
	 * @var string
242
	 */
243
	protected $tree_view_has_children_column;
244
245
	/**
246
	 * @var bool
247
	 */
248
	protected $outer_filter_rendering = FALSE;
249
250
	/**
251
	 * @var array
252
	 */
253
	protected $columns_export_order = [];
254
255
	/**
256
	 * @var bool
257
	 */
258
	protected $remember_state = TRUE;
259
260
	/**
261
	 * @var bool
262
	 */
263
	protected $refresh_url = TRUE;
264
265
	/**
266
	 * @var Nette\Http\SessionSection
267
	 */
268
	protected $grid_session;
269
270
	/**
271
	 * @var Column\ItemDetail
272
	 */
273
	protected $items_detail;
274
275
	/**
276
	 * @var array
277
	 */
278
	protected $row_conditions = [
279
		'group_action' => FALSE,
280
		'action' => []
281
	];
282
283
	/**
284
	 * @var array
285
	 */
286
	protected $column_callbacks = [];
287
288
	/**
289
	 * @var bool
290
	 */
291
	protected $can_hide_columns = FALSE;
292
293
	/**
294
	 * @var array
295
	 */
296
	protected $columns_visibility = [];
297
298
	/**
299
	 * @var InlineEdit
300
	 */
301
	protected $inlineEdit;
302
303
	/**
304
	 * @var InlineEdit
305
	 */
306
	protected $inlineAdd;
307
308
	/**
309
	 * @var bool
310
	 */
311
	protected $snippets_set = FALSE;
312
313
	/**
314
	 * @var bool
315
	 */
316
	protected $some_column_default_hide = FALSE;
317
318
	/**
319
	 * @var ColumnsSummary
320
	 */
321
	protected $columnsSummary;
322
323
	/**
324
	 * @var bool
325
	 */
326
	protected $auto_submit = TRUE;
327
328
	/**
329
	 * @var Filter\SubmitButton|NULL
330
	 */
331
	protected $filter_submit_button = NULL;
332
333
334
	/**
335
	 * @param Nette\ComponentModel\IContainer|NULL $parent
336
	 * @param string                               $name
337
	 */
338
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
339
	{
340
		parent::__construct($parent, $name);
341
342
		$this->monitor('Nette\Application\UI\Presenter');
343
344
		/**
345
		 * Try to find previous filters, pagination, per_page and other values in session
346
		 */
347
		$this->onRender[] = [$this, 'findSessionValues'];
348
349
		/**
350
		 * Find default filter values
351
		 */
352
		$this->onRender[] = [$this, 'findDefaultFilter'];
353
354
		/**
355
		 * Find default sort
356
		 */
357
		$this->onRender[] = [$this, 'findDefaultSort'];
358
359
		/**
360
		 * Find default items per page
361
		 */
362
		$this->onRender[] = [$this, 'findDefaultPerPage'];
363
	}
364
365
366
	/**
367
	 * {inheritDoc}
368
	 * @return void
369
	 */
370
	public function attached($presenter)
371
	{
372
		parent::attached($presenter);
373
374
		if ($presenter instanceof Nette\Application\UI\Presenter) {
375
			/**
376
			 * Get session
377
			 */
378
			if ($this->remember_state) {
379
				$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...
380
			}
381
		}
382
	}
383
384
385
	/********************************************************************************
386
	 *                                  RENDERING                                   *
387
	 ********************************************************************************/
388
389
390
	/**
391
	 * Render template
392
	 * @return void
393
	 */
394
	public function render()
395
	{
396
		/**
397
		 * Check whether datagrid has set some columns, initiated data source, etc
398
		 */
399
		if (!($this->dataModel instanceof DataModel)) {
400
			throw new DataGridException('You have to set a data source first.');
401
		}
402
403
		if (empty($this->columns)) {
404
			throw new DataGridException('You have to add at least one column.');
405
		}
406
407
		$this->getTemplate()->setTranslator($this->getTranslator());
408
409
		/**
410
		 * Invoke possible events
411
		 */
412
		$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...
413
414
		/**
415
		 * Prepare data for rendering (datagrid may render just one item)
416
		 */
417
		$rows = [];
418
419
		if (!empty($this->redraw_item)) {
420
			$items = $this->dataModel->filterRow($this->redraw_item);
421
		} else {
422
			$items = Nette\Utils\Callback::invokeArgs(
423
				[$this->dataModel, 'filterData'],
424
				[
425
					$this->getPaginator(),
426
					$this->createSorting($this->sort, $this->sort_callback),
427
					$this->assableFilters()
428
				]
429
			);
430
		}
431
432
		$callback = $this->rowCallback ?: NULL;
433
		$hasGroupActionOnRows = FALSE;
434
435
		foreach ($items as $item) {
436
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
437
438
			if (!$hasGroupActionOnRows && $row->hasGroupAction()){
439
				$hasGroupActionOnRows = TRUE;
440
			}
441
			
442
			if ($callback) {
443
				$callback($item, $row->getControl());
444
			}
445
446
			/**
447
			 * Walkaround for item snippet - snippet is the <tr> element and its class has to be also updated
448
			 */
449
			if (!empty($this->redraw_item)) {
450
				$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...
451
				$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...
452
			}
453
		}
454
455
		if ($hasGroupActionOnRows){
456
			$hasGroupActionOnRows = $this->hasGroupActions();
457
		}
458
459
		if ($this->isTreeView()) {
460
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
461
		}
462
463
		$this->getTemplate()->add('rows', $rows);
464
465
		$this->getTemplate()->add('columns', $this->getColumns());
466
		$this->getTemplate()->add('actions', $this->actions);
467
		$this->getTemplate()->add('exports', $this->exports);
468
		$this->getTemplate()->add('filters', $this->filters);
469
		$this->getTemplate()->add('toolbar_buttons', $this->toolbar_buttons);
470
471
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
472
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
473
		//$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...
474
		$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...
475
		$this->getTemplate()->add('items_detail', $this->items_detail);
476
		$this->getTemplate()->add('columns_visibility', $this->getColumnsVisibility());
477
		$this->getTemplate()->add('columnsSummary', $this->columnsSummary);
478
479
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
480
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
481
482
		$this->getTemplate()->add('hasGroupActionOnRows', $hasGroupActionOnRows);
483
484
		/**
485
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
486
		 */
487
		$this->getTemplate()->add('filter', $this['filter']);
488
489
		/**
490
		 * Set template file and render it
491
		 */
492
		$this->getTemplate()->setFile($this->getTemplateFile());
493
		$this->getTemplate()->render();
494
	}
495
496
497
	/********************************************************************************
498
	 *                                 ROW CALLBACK                                 *
499
	 ********************************************************************************/
500
501
502
	/**
503
	 * Each row can be modified with user callback
504
	 * @param  callable  $callback
505
	 * @return static
506
	 */
507
	public function setRowCallback(callable $callback)
508
	{
509
		$this->rowCallback = $callback;
510
511
		return $this;
512
	}
513
514
515
	/********************************************************************************
516
	 *                                 DATA SOURCE                                  *
517
	 ********************************************************************************/
518
519
520
	/**
521
	 * By default ID, you can change that
522
	 * @param string $primary_key
523
	 * @return static
524
	 */
525
	public function setPrimaryKey($primary_key)
526
	{
527
		if ($this->dataModel instanceof DataModel) {
528
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
529
		}
530
531
		$this->primary_key = $primary_key;
532
533
		return $this;
534
	}
535
536
537
	/**
538
	 * Set Grid data source
539
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
540
	 * @return static
541
	 */
542
	public function setDataSource($source)
543
	{
544
		$this->dataModel = new DataModel($source, $this->primary_key);
545
546
		return $this;
547
	}
548
549
550
	/********************************************************************************
551
	 *                                  TEMPLATING                                  *
552
	 ********************************************************************************/
553
554
555
	/**
556
	 * Set custom template file to render
557
	 * @param string $template_file
558
	 * @return static
559
	 */
560
	public function setTemplateFile($template_file)
561
	{
562
		$this->template_file = $template_file;
563
564
		return $this;
565
	}
566
567
568
	/**
569
	 * Get DataGrid template file
570
	 * @return string
571
	 * @return static
572
	 */
573
	public function getTemplateFile()
574
	{
575
		return $this->template_file ?: $this->getOriginalTemplateFile();
576
	}
577
578
579
	/**
580
	 * Get DataGrid original template file
581
	 * @return string
582
	 */
583
	public function getOriginalTemplateFile()
584
	{
585
		return __DIR__.'/templates/datagrid.latte';
586
	}
587
588
589
	/**
590
	 * Tell datagrid wheteher to use or not happy components
591
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
592
	 * @return void|bool
593
	 */
594
	public function useHappyComponents($use = NULL)
595
	{
596
		if (NULL === $use) {
597
			return $this->use_happy_components;
598
		}
599
600
		$this->use_happy_components = (bool) $use;
601
	}
602
603
604
	/********************************************************************************
605
	 *                                   SORTING                                    *
606
	 ********************************************************************************/
607
608
609
	/**
610
	 * Set default sorting
611
	 * @param array $sort
612
	 * @return static
613
	 */
614
	public function setDefaultSort($sort)
615
	{
616
		if (is_string($sort)) {
617
			$sort = [$sort => 'ASC'];
618
		} else {
619
			$sort = (array) $sort;
620
		}
621
622
		$this->default_sort = $sort;
623
624
		return $this;
625
	}
626
627
628
	/**
629
	 * User may set default sorting, apply it
630
	 * @return void
631
	 */
632
	public function findDefaultSort()
633
	{
634
		if (!empty($this->sort)) {
635
			return;
636
		}
637
638
		if (!empty($this->default_sort)) {
639
			$this->sort = $this->default_sort;
640
		}
641
642
		$this->saveSessionData('_grid_sort', $this->sort);
643
	}
644
645
646
	/**
647
	 * Set grido to be sortable
648
	 * @param bool $sortable
649
	 * @return static
650
	 */
651
	public function setSortable($sortable = TRUE)
652
	{
653
		if ($this->getItemsDetail()) {
654
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
655
		}
656
657
		$this->sortable = (bool) $sortable;
658
659
		return $this;
660
	}
661
662
663
	/**
664
	 * Set sortable handle
665
	 * @param string $handler
666
	 * @return static
667
	 */
668
	public function setSortableHandler($handler = 'sort!')
669
	{
670
		$this->sortable_handler = (string) $handler;
671
672
		return $this;
673
	}
674
675
676
	/**
677
	 * Tell whether DataGrid is sortable
678
	 * @return bool
679
	 */
680
	public function isSortable()
681
	{
682
		return $this->sortable;
683
	}
684
685
	/**
686
	 * Return sortable handle name
687
	 * @return string
688
	 */
689
	public function getSortableHandler()
690
	{
691
		return $this->sortable_handler;
692
	}
693
694
695
	protected function createSorting(array $sort, $sort_callback)
696
	{
697
		foreach ($sort as $key => $order) {
698
			$column = $this->columns[$key];
699
			$sort = [$column->getSortingColumn() => $order];
700
		}
701
702
		return new Sorting($sort, $sort_callback);
703
	}
704
705
706
	/********************************************************************************
707
	 *                                  TREE VIEW                                   *
708
	 ********************************************************************************/
709
710
711
	/**
712
	 * Is tree view set?
713
	 * @return boolean
714
	 */
715
	public function isTreeView()
716
	{
717
		return (bool) $this->tree_view_children_callback;
718
	}
719
720
721
	/**
722
	 * Setting tree view
723
	 * @param callable $get_children_callback
724
	 * @param string|callable $tree_view_has_children_column
725
	 * @return static
726
	 */
727
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
728
	{
729
		if (!is_callable($get_children_callback)) {
730
			throw new DataGridException(
731
				'Parameters to method DataGrid::setTreeView must be of type callable'
732
			);
733
		}
734
735
		if (is_callable($tree_view_has_children_column)) {
736
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
737
			$tree_view_has_children_column = NULL;
738
		}
739
740
		$this->tree_view_children_callback = $get_children_callback;
741
		$this->tree_view_has_children_column = $tree_view_has_children_column;
742
743
		/**
744
		 * TUrn off pagination
745
		 */
746
		$this->setPagination(FALSE);
747
748
		/**
749
		 * Set tree view template file
750
		 */
751
		if (!$this->template_file) {
752
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
753
		}
754
755
		return $this;
756
	}
757
758
759
	/**
760
	 * Is tree view children callback set?
761
	 * @return boolean
762
	 */
763
	public function hasTreeViewChildrenCallback()
764
	{
765
		return is_callable($this->tree_view_has_children_callback);
766
	}
767
768
769
	/**
770
	 * @param  mixed $item
771
	 * @return boolean
772
	 */
773
	public function treeViewChildrenCallback($item)
774
	{
775
		return call_user_func($this->tree_view_has_children_callback, $item);
776
	}
777
778
779
	/********************************************************************************
780
	 *                                    COLUMNS                                   *
781
	 ********************************************************************************/
782
783
784
	/**
785
	 * Add text column with no other formating
786
	 * @param  string      $key
787
	 * @param  string      $name
788
	 * @param  string|null $column
789
	 * @return Column\ColumnText
790
	 */
791
	public function addColumnText($key, $name, $column = NULL)
792
	{
793
		$this->addColumnCheck($key);
794
		$column = $column ?: $key;
795
796
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
797
	}
798
799
800
	/**
801
	 * Add column with link
802
	 * @param  string      $key
803
	 * @param  string      $name
804
	 * @param  string|null $column
805
	 * @return Column\ColumnLink
806
	 */
807
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
808
	{
809
		$this->addColumnCheck($key);
810
		$column = $column ?: $key;
811
		$href = $href ?: $key;
812
813
		if (NULL === $params) {
814
			$params = [$this->primary_key];
815
		}
816
817
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
818
	}
819
820
821
	/**
822
	 * Add column with possible number formating
823
	 * @param  string      $key
824
	 * @param  string      $name
825
	 * @param  string|null $column
826
	 * @return Column\ColumnNumber
827
	 */
828
	public function addColumnNumber($key, $name, $column = NULL)
829
	{
830
		$this->addColumnCheck($key);
831
		$column = $column ?: $key;
832
833
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
834
	}
835
836
837
	/**
838
	 * Add column with date formating
839
	 * @param  string      $key
840
	 * @param  string      $name
841
	 * @param  string|null $column
842
	 * @return Column\ColumnDateTime
843
	 */
844
	public function addColumnDateTime($key, $name, $column = NULL)
845
	{
846
		$this->addColumnCheck($key);
847
		$column = $column ?: $key;
848
849
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
850
	}
851
852
853
	/**
854
	 * Add column status
855
	 * @param  string      $key
856
	 * @param  string      $name
857
	 * @param  string|null $column
858
	 * @return Column\ColumnStatus
859
	 */
860
	public function addColumnStatus($key, $name, $column = NULL)
861
	{
862
		$this->addColumnCheck($key);
863
		$column = $column ?: $key;
864
865
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
866
	}
867
868
869
	/**
870
	 * @param string $key
871
	 * @param Column\Column $column
872
	 * @return Column\Column
873
	 */
874
	protected function addColumn($key, Column\Column $column)
875
	{
876
		$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...
877
878
		$this->columns_visibility[$key] = [
879
			'visible' => TRUE
880
		];
881
882
		return $this->columns[$key] = $column;
883
	}
884
885
886
	/**
887
	 * Return existing column
888
	 * @param  string $key
889
	 * @return Column\Column
890
	 * @throws DataGridException
891
	 */
892
	public function getColumn($key)
893
	{
894
		if (!isset($this->columns[$key])) {
895
			throw new DataGridException("There is no column at key [$key] defined.");
896
		}
897
898
		return $this->columns[$key];
899
	}
900
901
902
	/**
903
	 * Remove column
904
	 * @param string $key
905
	 * @return void
906
	 */
907
	public function removeColumn($key)
908
	{
909
		unset($this->columns[$key]);
910
	}
911
912
913
	/**
914
	 * Check whether given key already exists in $this->columns
915
	 * @param  string $key
916
	 * @throws DataGridException
917
	 */
918
	protected function addColumnCheck($key)
919
	{
920
		if (isset($this->columns[$key])) {
921
			throw new DataGridException("There is already column at key [$key] defined.");
922
		}
923
	}
924
925
926
	/********************************************************************************
927
	 *                                    ACTIONS                                   *
928
	 ********************************************************************************/
929
930
931
	/**
932
	 * Create action
933
	 * @param string     $key
934
	 * @param string     $name
935
	 * @param string     $href
936
	 * @param array|null $params
937
	 * @return Column\Action
938
	 */
939
	public function addAction($key, $name, $href = NULL, array $params = NULL)
940
	{
941
		$this->addActionCheck($key);
942
		$href = $href ?: $key;
943
944
		if (NULL === $params) {
945
			$params = [$this->primary_key];
946
		}
947
948
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
949
	}
950
951
952
	/**
953
	 * Create action callback
954
	 * @param string     $key
955
	 * @param string     $name
956
	 * @return Column\Action
957
	 */
958
	public function addActionCallback($key, $name, $callback = NULL)
959
	{
960
		$this->addActionCheck($key);
961
		$params = ['__id' => $this->primary_key];
962
963
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
964
965
		if ($callback) {
966
			if (!is_callable($callback)) {
967
				throw new DataGridException('ActionCallback callback has to be callable.');
968
			}
969
970
			$action->onClick[] = $callback;
971
		}
972
973
		return $action;
974
	}
975
976
977
	/**
978
	 * Get existing action
979
	 * @param  string       $key
980
	 * @return Column\Action
981
	 * @throws DataGridException
982
	 */
983
	public function getAction($key)
984
	{
985
		if (!isset($this->actions[$key])) {
986
			throw new DataGridException("There is no action at key [$key] defined.");
987
		}
988
989
		return $this->actions[$key];
990
	}
991
992
993
	/**
994
	 * Remove action
995
	 * @param string $key
996
	 * @return void
997
	 */
998
	public function removeAction($key)
999
	{
1000
		unset($this->actions[$key]);
1001
	}
1002
1003
1004
	/**
1005
	 * Check whether given key already exists in $this->filters
1006
	 * @param  string $key
1007
	 * @throws DataGridException
1008
	 */
1009
	protected function addActionCheck($key)
1010
	{
1011
		if (isset($this->actions[$key])) {
1012
			throw new DataGridException("There is already action at key [$key] defined.");
1013
		}
1014
	}
1015
1016
1017
	/********************************************************************************
1018
	 *                                    FILTERS                                   *
1019
	 ********************************************************************************/
1020
1021
1022
	/**
1023
	 * Add filter fot text search
1024
	 * @param string       $key
1025
	 * @param string       $name
1026
	 * @param array|string $columns
1027
	 * @return Filter\FilterText
1028
	 * @throws DataGridException
1029
	 */
1030
	public function addFilterText($key, $name, $columns = NULL)
1031
	{
1032
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
1033
1034
		if (!is_array($columns)) {
1035
			throw new DataGridException("Filter Text can except only array or string.");
1036
		}
1037
1038
		$this->addFilterCheck($key);
1039
1040
		return $this->filters[$key] = new Filter\FilterText($this, $key, $name, $columns);
1041
	}
1042
1043
1044
	/**
1045
	 * Add select box filter
1046
	 * @param string $key
1047
	 * @param string $name
1048
	 * @param array  $options
1049
	 * @param string $column
1050
	 * @return Filter\FilterSelect
1051
	 * @throws DataGridException
1052
	 */
1053 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...
1054
	{
1055
		$column = $column ?: $key;
1056
1057
		if (!is_string($column)) {
1058
			throw new DataGridException("Filter Select can only filter in one column.");
1059
		}
1060
1061
		$this->addFilterCheck($key);
1062
1063
		return $this->filters[$key] = new Filter\FilterSelect($this, $key, $name, $options, $column);
1064
	}
1065
1066
1067
	/**
1068
	 * Add multi select box filter
1069
	 * @param string $key
1070
	 * @param string $name
1071
	 * @param array  $options
1072
	 * @param string $column
1073
	 * @return Filter\FilterSelect
1074
	 * @throws DataGridException
1075
	 */
1076 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...
1077
	{
1078
		$column = $column ?: $key;
1079
1080
		if (!is_string($column)) {
1081
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1082
		}
1083
1084
		$this->addFilterCheck($key);
1085
1086
		return $this->filters[$key] = new Filter\FilterMultiSelect($this, $key, $name, $options, $column);
1087
	}
1088
1089
1090
	/**
1091
	 * Add datepicker filter
1092
	 * @param string $key
1093
	 * @param string $name
1094
	 * @param string $column
1095
	 * @return Filter\FilterDate
1096
	 * @throws DataGridException
1097
	 */
1098 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...
1099
	{
1100
		$column = $column ?: $key;
1101
1102
		if (!is_string($column)) {
1103
			throw new DataGridException("FilterDate can only filter in one column.");
1104
		}
1105
1106
		$this->addFilterCheck($key);
1107
1108
		return $this->filters[$key] = new Filter\FilterDate($this, $key, $name, $column);
1109
	}
1110
1111
1112
	/**
1113
	 * Add range filter (from - to)
1114
	 * @param string $key
1115
	 * @param string $name
1116
	 * @param string $column
1117
	 * @return Filter\FilterRange
1118
	 * @throws DataGridException
1119
	 */
1120 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...
1121
	{
1122
		$column = $column ?: $key;
1123
1124
		if (!is_string($column)) {
1125
			throw new DataGridException("FilterRange can only filter in one column.");
1126
		}
1127
1128
		$this->addFilterCheck($key);
1129
1130
		return $this->filters[$key] = new Filter\FilterRange($this, $key, $name, $column, $name_second);
1131
	}
1132
1133
1134
	/**
1135
	 * Add datepicker filter (from - to)
1136
	 * @param string $key
1137
	 * @param string $name
1138
	 * @param string $column
1139
	 * @return Filter\FilterDateRange
1140
	 * @throws DataGridException
1141
	 */
1142 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...
1143
	{
1144
		$column = $column ?: $key;
1145
1146
		if (!is_string($column)) {
1147
			throw new DataGridException("FilterDateRange can only filter in one column.");
1148
		}
1149
1150
		$this->addFilterCheck($key);
1151
1152
		return $this->filters[$key] = new Filter\FilterDateRange($this, $key, $name, $column, $name_second);
1153
	}
1154
1155
1156
	/**
1157
	 * Check whether given key already exists in $this->filters
1158
	 * @param  string $key
1159
	 * @throws DataGridException
1160
	 */
1161
	protected function addFilterCheck($key)
1162
	{
1163
		if (isset($this->filters[$key])) {
1164
			throw new DataGridException("There is already action at key [$key] defined.");
1165
		}
1166
	}
1167
1168
1169
	/**
1170
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1171
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1172
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1173
	 */
1174
	public function assableFilters()
1175
	{
1176
		foreach ($this->filter as $key => $value) {
1177
			if (!isset($this->filters[$key])) {
1178
				$this->deleteSesssionData($key);
1179
1180
				continue;
1181
			}
1182
1183
			if (is_array($value) || $value instanceof \Traversable) {
1184
				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...
1185
					$this->filters[$key]->setValue($value);
1186
				}
1187
			} else {
1188
				if ($value !== '' && $value !== NULL) {
1189
					$this->filters[$key]->setValue($value);
1190
				}
1191
			}
1192
		}
1193
1194
		foreach ($this->columns as $key => $column) {
1195
			if (isset($this->sort[$key])) {
1196
				$column->setSort($this->sort);
1197
			}
1198
		}
1199
1200
		return $this->filters;
1201
	}
1202
1203
1204
	/**
1205
	 * Remove filter
1206
	 * @param string $key
1207
	 * @return void
1208
	 */
1209
	public function removeFilter($key)
1210
	{
1211
		unset($this->filters[$key]);
1212
	}
1213
1214
1215
	/**
1216
	 * Get defined filter
1217
	 * @param  string $key
1218
	 * @return Filter\Filter
1219
	 */
1220
	public function getFilter($key)
1221
	{
1222
		if (!isset($this->filters[$key])) {
1223
			throw new DataGridException("Filter [{$key}] is not defined");
1224
		}
1225
1226
		return $this->filters[$key];
1227
	}
1228
1229
1230
	public function setStrictSessionFilterValues($strict = TRUE)
1231
	{
1232
		$this->strict_session_filter_values = (bool) $strict;
1233
	}
1234
1235
1236
	/********************************************************************************
1237
	 *                                  FILTERING                                   *
1238
	 ********************************************************************************/
1239
1240
1241
	/**
1242
	 * Is filter active?
1243
	 * @return boolean
1244
	 */
1245
	public function isFilterActive()
1246
	{
1247
		$is_filter = ArraysHelper::testTruthy($this->filter);
1248
1249
		return ($is_filter) || $this->force_filter_active;
1250
	}
1251
1252
1253
	/**
1254
	 * Tell that filter is active from whatever reasons
1255
	 * return static
1256
	 */
1257
	public function setFilterActive()
1258
	{
1259
		$this->force_filter_active = TRUE;
1260
1261
		return $this;
1262
	}
1263
1264
1265
	/**
1266
	 * Set filter values (force - overwrite user data)
1267
	 * @param array $filter
1268
	 * @return static
1269
	 */
1270
	public function setFilter(array $filter)
1271
	{
1272
		$this->filter = $filter;
1273
1274
		$this->saveSessionData('_grid_has_filtered', 1);
1275
1276
		return $this;
1277
	}
1278
1279
1280
	/**
1281
	 * If we want to sent some initial filter
1282
	 * @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...
1283
	 * @param bool  $use_on_reset
1284
	 * @return static
1285
	 */
1286
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1287
	{
1288
		foreach ($default_filter as $key => $value) {
1289
			$filter = $this->getFilter($key);
1290
1291
			if (!$filter) {
1292
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1293
			}
1294
1295
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1296
				throw new DataGridException(
1297
					"Default value of filter [$key] - MultiSelect has to be an array"
1298
				);
1299
			}
1300
1301
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1302
				if (!is_array($value)) {
1303
					throw new DataGridException(
1304
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1305
					);
1306
				}
1307
1308
				$temp = $value;
1309
				unset($temp['from'], $temp['to']);
1310
1311
				if (!empty($temp)) {
1312
					throw new DataGridException(
1313
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1314
					);
1315
				}
1316
			}
1317
		}
1318
1319
		$this->default_filter = $default_filter;
1320
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1321
1322
		return $this;
1323
	}
1324
1325
1326
	/**
1327
	 * User may set default filter, find it
1328
	 * @return void
1329
	 */
1330
	public function findDefaultFilter()
1331
	{
1332
		if (!empty($this->filter)) {
1333
			return;
1334
		}
1335
1336
		if ($this->getSessionData('_grid_has_filtered')) {
1337
			return;
1338
		}
1339
1340
		if (!empty($this->default_filter)) {
1341
			$this->filter = $this->default_filter;
1342
		}
1343
1344
		foreach ($this->filter as $key => $value) {
1345
			$this->saveSessionData($key, $value);
1346
		}
1347
	}
1348
1349
1350
	/**
1351
	 * FilterAndGroupAction form factory
1352
	 * @return Form
1353
	 */
1354
	public function createComponentFilter()
1355
	{
1356
		$form = new Form($this, 'filter');
1357
1358
		$form->setMethod(static::$form_method);
1359
1360
		$form->setTranslator($this->getTranslator());
1361
1362
		/**
1363
		 * InlineEdit part
1364
		 */
1365
		$inline_edit_container = $form->addContainer('inline_edit');
1366
1367 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...
1368
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1369
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1370
				->setValidationScope(FALSE);
1371
1372
			$this->inlineEdit->onControlAdd($inline_edit_container);
1373
			$this->inlineEdit->onControlAfterAdd($inline_edit_container);
1374
		}
1375
1376
		/**
1377
		 * InlineAdd part
1378
		 */
1379
		$inline_add_container = $form->addContainer('inline_add');
1380
1381 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...
1382
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1383
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1384
				->setValidationScope(FALSE)
1385
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1386
1387
			$this->inlineAdd->onControlAdd($inline_add_container);
1388
			$this->inlineAdd->onControlAfterAdd($inline_add_container);
1389
		}
1390
1391
		/**
1392
		 * ItemDetail form part
1393
		 */
1394
		$items_detail_form = $this->getItemDetailForm();
1395
1396
		if ($items_detail_form instanceof Nette\Forms\Container) {
1397
			$form['items_detail_form'] = $items_detail_form;
1398
		}
1399
1400
		/**
1401
		 * Filter part
1402
		 */
1403
		$filter_container = $form->addContainer('filter');
1404
1405
		foreach ($this->filters as $filter) {
1406
			$filter->addToFormContainer($filter_container);
1407
		}
1408
1409
		if (!$this->hasAutoSubmit()) {
1410
			$filter_container['submit'] = $this->getFilterSubmitButton();
1411
		}
1412
1413
		/**
1414
		 * Group action part
1415
		 */
1416
		$group_action_container = $form->addContainer('group_action');
1417
1418
		if ($this->hasGroupActions()) {
1419
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1420
		}
1421
1422
		$this->setFilterContainerDefaults($form['filter'], $this->filter);
1423
1424
		/**
1425
		 * Per page part
1426
		 */
1427
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1428
			->setTranslator(NULL);
1429
1430
		if (!$form->isSubmitted()) {
1431
			$form['per_page']->setValue($this->getPerPage());
1432
		}
1433
1434
		$form->addSubmit('per_page_submit', '');
1435
		
1436
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1437
1438
		return $form;
1439
	}
1440
1441
1442
	public function setFilterContainerDefaults(Nette\Forms\Container $container, array $values)
1443
	{
1444
		foreach ($container->getComponents() as $name => $control) {
1445
			if ($control instanceof Nette\Forms\IControl) {
1446 View Code Duplication
				if (array_key_exists($name, $values)) {
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...
1447
					try {
1448
						$control->setValue($values[$name]);
1449
					} catch (Nette\InvalidArgumentException $e) {
1450
						if ($this->strict_session_filter_values) {
1451
							throw $e;
1452
						}
1453
					}
1454
				}
1455
1456
			} elseif ($control instanceof Nette\Forms\Container) {
1457 View Code Duplication
				if (array_key_exists($name, $values)) {
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...
1458
					try {
1459
						$control->setValue($values[$name]);
0 ignored issues
show
Bug introduced by
The method setValue() does not exist on Nette\Forms\Container. Did you maybe mean setValues()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1460
					} catch (Nette\InvalidArgumentException $e) {
1461
						if ($this->strict_session_filter_values) {
1462
							throw $e;
1463
						}
1464
					}
1465
				}
1466
			}
1467
		}
1468
	}
1469
1470
1471
	/**
1472
	 * Set $this->filter values after filter form submitted
1473
	 * @param  Form $form
1474
	 * @return void
1475
	 */
1476
	public function filterSucceeded(Form $form)
1477
	{
1478
		if ($this->snippets_set) {
1479
			return;
1480
		}
1481
1482
		$values = $form->getValues();
1483
1484
		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...
1485
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1486
				return;
1487
			}
1488
		}
1489
1490
		/**
1491
		 * Per page
1492
		 */
1493
		$this->saveSessionData('_grid_per_page', $values->per_page);
1494
		$this->per_page = $values->per_page;
1495
1496
		/**
1497
		 * Inline edit
1498
		 */
1499
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1500
			$edit = $form['inline_edit'];
1501
1502
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1503
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1504
				$primary_where_column = $form->getHttpData(
1505
					Form::DATA_LINE,
1506
					'inline_edit[_primary_where_column]'
1507
				);
1508
1509
				if ($edit['submit']->isSubmittedBy()) {
1510
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1511
					$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...
1512
				} else {
1513
					$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...
1514
				}
1515
1516
				if ($edit['submit']->isSubmittedBy() && !empty($this->inlineEdit->onCustomRedraw)) {
1517
					$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...
1518
				} else {
1519
					$this->redrawItem($id, $primary_where_column);
1520
				}
1521
1522
				return;
1523
			}
1524
		}
1525
1526
		/**
1527
		 * Inline add
1528
		 */
1529
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1530
			$add = $form['inline_add'];
1531
1532
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1533
				if ($add['submit']->isSubmittedBy()) {
1534
					$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...
1535
1536
					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...
1537
						$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...
1538
					}
1539
				}
1540
1541
				return;
1542
			}
1543
		}
1544
1545
		/**
1546
		 * Filter itself
1547
		 */
1548
		$values = $values['filter'];
1549
1550
		foreach ($values as $key => $value) {
1551
			/**
1552
			 * Session stuff
1553
			 */
1554
			$this->saveSessionData($key, $value);
1555
1556
			/**
1557
			 * Other stuff
1558
			 */
1559
			$this->filter[$key] = $value;
1560
		}
1561
1562
		if (!empty($values)) {
1563
			$this->saveSessionData('_grid_has_filtered', 1);
1564
		}
1565
1566
		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...
1567
			$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...
1568
1569
			foreach ($this->columns as $key => $column) {
1570
				if ($column->isSortable()) {
1571
					$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...
1572
						'sort' => $column->getSortNext()
1573
					]);
1574
				}
1575
			}
1576
		}
1577
1578
		$this->reload();
1579
	}
1580
1581
1582
	/**
1583
	 * Should be datagrid filters rendered separately?
1584
	 * @param boolean $out
1585
	 * @return static
1586
	 */
1587
	public function setOuterFilterRendering($out = TRUE)
1588
	{
1589
		$this->outer_filter_rendering = (bool) $out;
1590
1591
		return $this;
1592
	}
1593
1594
1595
	/**
1596
	 * Are datagrid filters rendered separately?
1597
	 * @return boolean
1598
	 */
1599
	public function hasOuterFilterRendering()
1600
	{
1601
		return $this->outer_filter_rendering;
1602
	}
1603
1604
1605
	/**
1606
	 * Try to restore session stuff
1607
	 * @return void
1608
	 */
1609
	public function findSessionValues()
1610
	{
1611
		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...
1612
			return;
1613
		}
1614
1615
		if (!$this->remember_state) {
1616
			return;
1617
		}
1618
1619
		if ($page = $this->getSessionData('_grid_page')) {
1620
			$this->page = $page;
1621
		}
1622
1623
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1624
			$this->per_page = $per_page;
1625
		}
1626
1627
		if ($sort = $this->getSessionData('_grid_sort')) {
1628
			$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...
1629
		}
1630
1631
		foreach ($this->getSessionData() as $key => $value) {
1632
			$other_session_keys = [
1633
				'_grid_per_page',
1634
				'_grid_sort',
1635
				'_grid_page',
1636
				'_grid_has_filtered',
1637
				'_grid_hidden_columns',
1638
				'_grid_hidden_columns_manipulated'
1639
			];
1640
1641
			if (!in_array($key, $other_session_keys)) {
1642
				try {
1643
					$this->getFilter($key);
1644
1645
					$this->filter[$key] = $value;
1646
1647
				} catch (DataGridException $e) {
1648
					if ($this->strict_session_filter_values) {
1649
						throw new DataGridException("Session filter: Filter [$key] not found");
1650
					}
1651
				}
1652
			}
1653
		}
1654
1655
		/**
1656
		 * When column is sorted via custom callback, apply it
1657
		 */
1658
		if (empty($this->sort_callback) && !empty($this->sort)) {
1659
			foreach ($this->sort as $key => $order) {
1660
				try {
1661
					$column = $this->getColumn($key);
1662
1663
				} catch (DataGridException $e) {
1664
					$this->deleteSesssionData('_grid_sort');
1665
					$this->sort = [];
1666
1667
					return;
1668
				}
1669
1670
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1671
					$this->sort_callback = $column->getSortableCallback();
1672
				}
1673
			}
1674
		}
1675
	}
1676
1677
1678
	/********************************************************************************
1679
	 *                                    EXPORTS                                   *
1680
	 ********************************************************************************/
1681
1682
1683
	/**
1684
	 * Add export of type callback
1685
	 * @param string $text
1686
	 * @param callable $callback
1687
	 * @param boolean $filtered
1688
	 * @return Export\Export
1689
	 */
1690
	public function addExportCallback($text, $callback, $filtered = FALSE)
1691
	{
1692
		if (!is_callable($callback)) {
1693
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1694
		}
1695
1696
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1697
	}
1698
1699
1700
	/**
1701
	 * Add already implemented csv export
1702
	 * @param string $text
1703
	 * @param string $csv_file_name
1704
	 * @param string|null $output_encoding
1705
	 * @param string|null $delimiter
1706
	 * @param bool $include_bom
1707
	 * @return Export\Export
1708
	 */
1709 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...
1710
	{
1711
		return $this->addToExports(new Export\ExportCsv(
1712
			$this,
1713
			$text,
1714
			$csv_file_name,
1715
			FALSE,
1716
			$output_encoding,
1717
			$delimiter,
1718
			$include_bom
1719
		));
1720
	}
1721
1722
1723
	/**
1724
	 * Add already implemented csv export, but for filtered data
1725
	 * @param string $text
1726
	 * @param string $csv_file_name
1727
	 * @param string|null $output_encoding
1728
	 * @param string|null $delimiter
1729
	 * @param bool $include_bom
1730
	 * @return Export\Export
1731
	 */
1732 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...
1733
	{
1734
		return $this->addToExports(new Export\ExportCsv(
1735
			$this,
1736
			$text,
1737
			$csv_file_name,
1738
			TRUE,
1739
			$output_encoding,
1740
			$delimiter,
1741
			$include_bom
1742
		));
1743
	}
1744
1745
1746
	/**
1747
	 * Add export to array
1748
	 * @param Export\Export $export
1749
	 * @return Export\Export
1750
	 */
1751
	protected function addToExports(Export\Export $export)
1752
	{
1753
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1754
1755
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1756
1757
		return $this->exports[$id] = $export;
1758
	}
1759
1760
1761
	public function resetExportsLinks()
1762
	{
1763
		foreach ($this->exports as $id => $export) {
1764
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1765
		}
1766
	}
1767
1768
1769
	/********************************************************************************
1770
	 *                                TOOLBAR BUTTONS                               *
1771
	 ********************************************************************************/
1772
1773
1774
	/**
1775
	 * Add toolbar button
1776
	 * @param string $href
1777
	 * @param string $text
1778
	 * @param array  $params
1779
	 * @return ToolbarButton
1780
	 */
1781
	public function addToolbarButton($href, $text = '', $params = [])
1782
	{
1783
		$button = new ToolbarButton($this, $href, $text, $params);
1784
1785
		return $this->toolbar_buttons[] = $button;
1786
	}
1787
1788
1789
	/********************************************************************************
1790
	 *                                 GROUP ACTIONS                                *
1791
	 ********************************************************************************/
1792
1793
1794
	/**
1795
	 * Alias for add group select action
1796
	 * @param string $title
1797
	 * @param array  $options
1798
	 * @return GroupAction\GroupAction
1799
	 */
1800
	public function addGroupAction($title, $options = [])
1801
	{
1802
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1803
	}
1804
1805
1806
	/**
1807
	 * Add group action (select box)
1808
	 * @param string $title
1809
	 * @param array  $options
1810
	 * @return GroupAction\GroupAction
1811
	 */
1812
	public function addGroupSelectAction($title, $options = [])
1813
	{
1814
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1815
	}
1816
1817
1818
	/**
1819
	 * Add group action (text input)
1820
	 * @param string $title
1821
	 * @return GroupAction\GroupAction
1822
	 */
1823
	public function addGroupTextAction($title)
1824
	{
1825
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1826
	}
1827
1828
1829
	/**
1830
	 * Add group action (textarea)
1831
	 * @param string $title
1832
	 * @return GroupAction\GroupAction
1833
	 */
1834
	public function addGroupTextareaAction($title)
1835
	{
1836
		return $this->getGroupActionCollection()->addGroupTextareaAction($title);
1837
	}
1838
1839
1840
	/**
1841
	 * Get collection of all group actions
1842
	 * @return GroupAction\GroupActionCollection
1843
	 */
1844
	public function getGroupActionCollection()
1845
	{
1846
		if (!$this->group_action_collection) {
1847
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1848
		}
1849
1850
		return $this->group_action_collection;
1851
	}
1852
1853
1854
	/**
1855
	 * Has datagrid some group actions?
1856
	 * @return boolean
1857
	 */
1858
	public function hasGroupActions()
1859
	{
1860
		return (bool) $this->group_action_collection;
1861
	}
1862
1863
1864
	/********************************************************************************
1865
	 *                                   HANDLERS                                   *
1866
	 ********************************************************************************/
1867
1868
1869
	/**
1870
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1871
	 * @param  int  $page
1872
	 * @return void
1873
	 */
1874
	public function handlePage($page)
1875
	{
1876
		/**
1877
		 * Session stuff
1878
		 */
1879
		$this->page = $page;
1880
		$this->saveSessionData('_grid_page', $page);
1881
1882
		$this->reload(['table']);
1883
	}
1884
1885
1886
	/**
1887
	 * Handler for sorting
1888
	 * @param array $sort
1889
	 * @return void
1890
	 */
1891
	public function handleSort(array $sort)
1892
	{
1893
		$new_sort = [];
1894
1895
		/**
1896
		 * Find apropirate column
1897
		 */
1898
		foreach ($sort as $key => $value) {
1899
			if (empty($this->columns[$key])) {
1900
				throw new DataGridException("Column <$key> not found");
1901
			}
1902
1903
			$column = $this->columns[$key];
1904
			$new_sort = [$key => $value];
1905
1906
			/**
1907
			 * Pagination may be reseted after sorting
1908
			 */
1909
			if ($column->sortableResetPagination()) {
1910
				$this->page = 1;
1911
				$this->saveSessionData('_grid_page', 1);
1912
			}
1913
1914
			/**
1915
			 * Custom sorting callback may be applied
1916
			 */
1917
			if ($column->getSortableCallback()) {
1918
				$this->sort_callback = $column->getSortableCallback();
1919
			}
1920
		}
1921
1922
		/**
1923
		 * Session stuff
1924
		 */
1925
		$this->sort = $new_sort;
1926
		$this->saveSessionData('_grid_sort', $this->sort);
1927
1928
		$this->reload(['table']);
1929
	}
1930
1931
1932
	/**
1933
	 * handler for reseting the filter
1934
	 * @return void
1935
	 */
1936
	public function handleResetFilter()
1937
	{
1938
		/**
1939
		 * Session stuff
1940
		 */
1941
		$this->deleteSesssionData('_grid_page');
1942
1943
		if ($this->default_filter_use_on_reset) {
1944
			$this->deleteSesssionData('_grid_has_filtered');
1945
		}
1946
1947
		foreach ($this->getSessionData() as $key => $value) {
1948
			if (!in_array($key, [
1949
				'_grid_per_page',
1950
				'_grid_sort',
1951
				'_grid_page',
1952
				'_grid_has_filtered',
1953
				'_grid_hidden_columns',
1954
				'_grid_hidden_columns_manipulated'
1955
				])) {
1956
1957
				$this->deleteSesssionData($key);
1958
			}
1959
		}
1960
1961
		$this->filter = [];
1962
1963
		$this->reload(['grid']);
1964
	}
1965
1966
1967
	/**
1968
	 * Handler for export
1969
	 * @param  int $id Key for particular export class in array $this->exports
1970
	 * @return void
1971
	 */
1972
	public function handleExport($id)
1973
	{
1974
		if (!isset($this->exports[$id])) {
1975
			throw new Nette\Application\ForbiddenRequestException;
1976
		}
1977
1978
		if (!empty($this->columns_export_order)) {
1979
			$this->setColumnsOrder($this->columns_export_order);
1980
		}
1981
1982
		$export = $this->exports[$id];
1983
1984
		if ($export->isFiltered()) {
1985
			$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...
1986
			$filter    = $this->assableFilters();
1987
		} else {
1988
			$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...
1989
			$filter    = [];
1990
		}
1991
1992
		if (NULL === $this->dataModel) {
1993
			throw new DataGridException('You have to set a data source first.');
1994
		}
1995
1996
		$rows = [];
1997
1998
		$items = Nette\Utils\Callback::invokeArgs(
1999
			[$this->dataModel, 'filterData'], [
2000
				NULL,
2001
				$this->createSorting($this->sort, $this->sort_callback),
2002
				$filter
2003
			]
2004
		);
2005
2006
		foreach ($items as $item) {
2007
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
2008
		}
2009
2010
		if ($export instanceof Export\ExportCsv) {
2011
			$export->invoke($rows);
2012
		} else {
2013
			$export->invoke($items);
2014
		}
2015
2016
		if ($export->isAjax()) {
2017
			$this->reload();
2018
		}
2019
	}
2020
2021
2022
	/**
2023
	 * Handler for getting children of parent item (e.g. category)
2024
	 * @param  int $parent
2025
	 * @return void
2026
	 */
2027
	public function handleGetChildren($parent)
2028
	{
2029
		$this->setDataSource(
2030
			call_user_func($this->tree_view_children_callback, $parent)
2031
		);
2032
2033
		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...
2034
			$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...
2035
			$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...
2036
2037
			$this->redrawControl('items');
2038
2039
			$this->onRedraw();
2040
		} else {
2041
			$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...
2042
		}
2043
	}
2044
2045
2046
	/**
2047
	 * Handler for getting item detail
2048
	 * @param  mixed $id
2049
	 * @return void
2050
	 */
2051
	public function handleGetItemDetail($id)
2052
	{
2053
		$this->getTemplate()->add('toggle_detail', $id);
2054
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
2055
2056
		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...
2057
			$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...
2058
			$this->redrawControl('items');
2059
2060
			$this->onRedraw();
2061
		} else {
2062
			$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...
2063
		}
2064
	}
2065
2066
2067
	/**
2068
	 * Handler for inline editing
2069
	 * @param  mixed $id
2070
	 * @param  mixed $key
2071
	 * @return void
2072
	 */
2073
	public function handleEdit($id, $key)
2074
	{
2075
		$column = $this->getColumn($key);
2076
		$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...
2077
2078
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
2079
	}
2080
2081
2082
	/**
2083
	 * Redraw $this
2084
	 * @return void
2085
	 */
2086
	public function reload($snippets = [])
2087
	{
2088
		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...
2089
			$this->redrawControl('tbody');
2090
			$this->redrawControl('pagination');
2091
2092
			/**
2093
			 * manualy reset exports links...
2094
			 */
2095
			$this->resetExportsLinks();
2096
			$this->redrawControl('exports');
2097
2098
			foreach ($snippets as $snippet) {
2099
				$this->redrawControl($snippet);
2100
			}
2101
2102
			$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...
2103
2104
			$this->onRedraw();
2105
		} else {
2106
			$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...
2107
		}
2108
	}
2109
2110
2111
	/**
2112
	 * Handler for column status
2113
	 * @param  string $id
2114
	 * @param  string $key
2115
	 * @param  string $value
2116
	 * @return void
2117
	 */
2118
	public function handleChangeStatus($id, $key, $value)
2119
	{
2120
		if (empty($this->columns[$key])) {
2121
			throw new DataGridException("ColumnStatus[$key] does not exist");
2122
		}
2123
2124
		$this->columns[$key]->onChange($id, $value);
2125
	}
2126
2127
2128
	/**
2129
	 * Redraw just one row via ajax
2130
	 * @param  int   $id
2131
	 * @param  mixed $primary_where_column
2132
	 * @return void
2133
	 */
2134
	public function redrawItem($id, $primary_where_column = NULL)
2135
	{
2136
		$this->snippets_set = TRUE;
2137
2138
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
2139
2140
		$this->redrawControl('items');
2141
2142
		$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...
2143
2144
		$this->onRedraw();
2145
	}
2146
2147
2148
	/**
2149
	 * Tell datagrid to display all columns
2150
	 * @return void
2151
	 */
2152
	public function handleShowAllColumns()
2153
	{
2154
		$this->deleteSesssionData('_grid_hidden_columns');
2155
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2156
2157
		$this->redrawControl();
2158
2159
		$this->onRedraw();
2160
	}
2161
2162
2163
	/**
2164
	 * Tell datagrid to display default columns
2165
	 * @return void
2166
	 */
2167
	public function handleShowDefaultColumns()
2168
	{
2169
		$this->deleteSesssionData('_grid_hidden_columns');
2170
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
2171
2172
		$this->redrawControl();
2173
2174
		$this->onRedraw();
2175
	}
2176
2177
2178
	/**
2179
	 * Reveal particular column
2180
	 * @param  string $column
2181
	 * @return void
2182
	 */
2183 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...
2184
	{
2185
		$columns = $this->getSessionData('_grid_hidden_columns');
2186
2187
		if (!empty($columns)) {
2188
			$pos = array_search($column, $columns);
2189
2190
			if ($pos !== FALSE) {
2191
				unset($columns[$pos]);
2192
			}
2193
		}
2194
2195
		$this->saveSessionData('_grid_hidden_columns', $columns);
2196
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2197
2198
		$this->redrawControl();
2199
2200
		$this->onRedraw();
2201
	}
2202
2203
2204
	/**
2205
	 * Notice datagrid to not display particular columns
2206
	 * @param  string $column
2207
	 * @return void
2208
	 */
2209 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...
2210
	{
2211
		/**
2212
		 * Store info about hiding a column to session
2213
		 */
2214
		$columns = $this->getSessionData('_grid_hidden_columns');
2215
2216
		if (empty($columns)) {
2217
			$columns = [$column];
2218
		} else if (!in_array($column, $columns)) {
2219
			array_push($columns, $column);
2220
		}
2221
2222
		$this->saveSessionData('_grid_hidden_columns', $columns);
2223
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2224
2225
		$this->redrawControl();
2226
2227
		$this->onRedraw();
2228
	}
2229
2230
2231
	public function handleActionCallback($__key, $__id)
2232
	{
2233
		$action = $this->getAction($__key);
2234
2235
		if (!($action instanceof Column\ActionCallback)) {
2236
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2237
		}
2238
2239
		$action->onClick($__id);
2240
	}
2241
2242
2243
	/********************************************************************************
2244
	 *                                  PAGINATION                                  *
2245
	 ********************************************************************************/
2246
2247
2248
	/**
2249
	 * Set options of select "items_per_page"
2250
	 * @param array $items_per_page_list
2251
	 * @return static
2252
	 */
2253
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
2254
	{
2255
		$this->items_per_page_list = $items_per_page_list;
2256
2257
		if ($include_all) {
2258
			$this->items_per_page_list[] = 'all';
2259
		}
2260
2261
		return $this;
2262
	}
2263
2264
2265
	/**
2266
	 * Set default "items per page" value in pagination select
2267
	 * @param $count
2268
	 * @return static
2269
	 */
2270
	public function setDefaultPerPage($count)
2271
	{
2272
		$this->default_per_page = $count;
2273
2274
		return $this;
2275
	}
2276
2277
2278
	/**
2279
	 * User may set default "items per page" value, apply it
2280
	 * @return void
2281
	 */
2282
	public function findDefaultPerPage()
2283
	{
2284
		if (!empty($this->per_page)) {
2285
			return;
2286
		}
2287
2288
		if (!empty($this->default_per_page)) {
2289
			$this->per_page = $this->default_per_page;
2290
		}
2291
2292
		$this->saveSessionData('_grid_per_page', $this->per_page);
2293
	}
2294
2295
2296
	/**
2297
	 * Paginator factory
2298
	 * @return Components\DataGridPaginator\DataGridPaginator
2299
	 */
2300
	public function createComponentPaginator()
2301
	{
2302
		/**
2303
		 * Init paginator
2304
		 */
2305
		$component = new Components\DataGridPaginator\DataGridPaginator(
2306
			$this->getTranslator(),
2307
			static::$icon_prefix
2308
		);
2309
		$paginator = $component->getPaginator();
2310
2311
		$paginator->setPage($this->page);
2312
		$paginator->setItemsPerPage($this->getPerPage());
2313
2314
		return $component;
2315
	}
2316
2317
2318
	/**
2319
	 * Get parameter per_page
2320
	 * @return int
2321
	 */
2322
	public function getPerPage()
2323
	{
2324
		$items_per_page_list = $this->getItemsPerPageList();
2325
2326
		$per_page = $this->per_page ?: reset($items_per_page_list);
2327
2328
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2329
			$per_page = reset($items_per_page_list);
2330
		}
2331
2332
		return $per_page;
2333
	}
2334
2335
2336
	/**
2337
	 * Get associative array of items_per_page_list
2338
	 * @return array
2339
	 */
2340
	public function getItemsPerPageList()
2341
	{
2342
		if (empty($this->items_per_page_list)) {
2343
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2344
		}
2345
2346
		$list = array_flip($this->items_per_page_list);
2347
2348
		foreach ($list as $key => $value) {
2349
			$list[$key] = $key;
2350
		}
2351
2352
		if (array_key_exists('all', $list)) {
2353
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2354
		}
2355
2356
		return $list;
2357
	}
2358
2359
2360
	/**
2361
	 * Order Grid to "be paginated"
2362
	 * @param bool $do
2363
	 * @return static
2364
	 */
2365
	public function setPagination($do)
2366
	{
2367
		$this->do_paginate = (bool) $do;
2368
2369
		return $this;
2370
	}
2371
2372
2373
	/**
2374
	 * Tell whether Grid is paginated
2375
	 * @return bool
2376
	 */
2377
	public function isPaginated()
2378
	{
2379
		return $this->do_paginate;
2380
	}
2381
2382
2383
	/**
2384
	 * Return current paginator class
2385
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2386
	 */
2387
	public function getPaginator()
2388
	{
2389
		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...
2390
			return $this['paginator'];
2391
		}
2392
2393
		return NULL;
2394
	}
2395
2396
2397
	/********************************************************************************
2398
	 *                                     I18N                                     *
2399
	 ********************************************************************************/
2400
2401
2402
	/**
2403
	 * Set datagrid translator
2404
	 * @param Nette\Localization\ITranslator $translator
2405
	 * @return static
2406
	 */
2407
	public function setTranslator(Nette\Localization\ITranslator $translator)
2408
	{
2409
		$this->translator = $translator;
2410
2411
		return $this;
2412
	}
2413
2414
2415
	/**
2416
	 * Get translator for datagrid
2417
	 * @return Nette\Localization\ITranslator
2418
	 */
2419
	public function getTranslator()
2420
	{
2421
		if (!$this->translator) {
2422
			$this->translator = new Localization\SimpleTranslator;
2423
		}
2424
2425
		return $this->translator;
2426
	}
2427
2428
2429
	/********************************************************************************
2430
	 *                                 COLUMNS ORDER                                *
2431
	 ********************************************************************************/
2432
2433
2434
	/**
2435
	 * Set order of datagrid columns
2436
	 * @param array $order
2437
	 * @return static
2438
	 */
2439
	public function setColumnsOrder($order)
2440
	{
2441
		$new_order = [];
2442
2443
		foreach ($order as $key) {
2444
			if (isset($this->columns[$key])) {
2445
				$new_order[$key] = $this->columns[$key];
2446
			}
2447
		}
2448
2449
		if (sizeof($new_order) === sizeof($this->columns)) {
2450
			$this->columns = $new_order;
2451
		} else {
2452
			throw new DataGridException('When changing columns order, you have to specify all columns');
2453
		}
2454
2455
		return $this;
2456
	}
2457
2458
2459
	/**
2460
	 * Columns order may be different for export and normal grid
2461
	 * @param array $order
2462
	 */
2463
	public function setColumnsExportOrder($order)
2464
	{
2465
		$this->columns_export_order = (array) $order;
2466
	}
2467
2468
2469
	/********************************************************************************
2470
	 *                                SESSION & URL                                 *
2471
	 ********************************************************************************/
2472
2473
2474
	/**
2475
	 * Find some unique session key name
2476
	 * @return string
2477
	 */
2478
	public function getSessionSectionName()
2479
	{
2480
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2481
	}
2482
2483
2484
	/**
2485
	 * Should datagrid remember its filters/pagination/etc using session?
2486
	 * @param bool $remember
2487
	 * @return static
2488
	 */
2489
	public function setRememberState($remember = TRUE)
2490
	{
2491
		$this->remember_state = (bool) $remember;
2492
2493
		return $this;
2494
	}
2495
2496
2497
	/**
2498
	 * Should datagrid refresh url using history API?
2499
	 * @param bool $refresh
2500
	 * @return static
2501
	 */
2502
	public function setRefreshUrl($refresh = TRUE)
2503
	{
2504
		$this->refresh_url = (bool) $refresh;
2505
2506
2507
		return $this;
2508
	}
2509
2510
2511
	/**
2512
	 * Get session data if functionality is enabled
2513
	 * @param  string $key
2514
	 * @return mixed
2515
	 */
2516
	public function getSessionData($key = NULL, $default_value = NULL)
2517
	{
2518
		if (!$this->remember_state) {
2519
			return $key ? $default_value : [];
2520
		}
2521
2522
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2523
	}
2524
2525
2526
	/**
2527
	 * Save session data - just if it is enabled
2528
	 * @param  string $key
2529
	 * @param  mixed  $value
2530
	 * @return void
2531
	 */
2532
	public function saveSessionData($key, $value)
2533
	{
2534
		if ($this->remember_state) {
2535
			$this->grid_session->{$key} = $value;
2536
		}
2537
	}
2538
2539
2540
	/**
2541
	 * Delete session data
2542
	 * @return void
2543
	 */
2544
	public function deleteSesssionData($key)
2545
	{
2546
		unset($this->grid_session->{$key});
2547
	}
2548
2549
2550
	/********************************************************************************
2551
	 *                                  ITEM DETAIL                                 *
2552
	 ********************************************************************************/
2553
2554
2555
	/**
2556
	 * Get items detail parameters
2557
	 * @return array
2558
	 */
2559
	public function getItemsDetail()
2560
	{
2561
		return $this->items_detail;
2562
	}
2563
2564
2565
	/**
2566
	 * Items can have thair detail - toggled
2567
	 * @param mixed $detail callable|string|bool
2568
	 * @param bool|NULL $primary_where_column
2569
	 * @return Column\ItemDetail
2570
	 */
2571
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2572
	{
2573
		if ($this->isSortable()) {
2574
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2575
		}
2576
2577
		$this->items_detail = new Column\ItemDetail(
2578
			$this,
2579
			$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...
2580
		);
2581
2582
		if (is_string($detail)) {
2583
			/**
2584
			 * Item detail will be in separate template
2585
			 */
2586
			$this->items_detail->setType('template');
2587
			$this->items_detail->setTemplate($detail);
2588
2589
		} else if (is_callable($detail)) {
2590
			/**
2591
			 * Item detail will be rendered via custom callback renderer
2592
			 */
2593
			$this->items_detail->setType('renderer');
2594
			$this->items_detail->setRenderer($detail);
2595
2596
		} else if (TRUE === $detail) {
2597
			/**
2598
			 * Item detail will be rendered probably via block #detail
2599
			 */
2600
			$this->items_detail->setType('block');
2601
2602
		} else {
2603
			throw new DataGridException(
2604
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2605
			);
2606
		}
2607
2608
		return $this->items_detail;
2609
	}
2610
2611
2612
	/**
2613
	 * @param callable $callable_set_container 
2614
	 * @return static
2615
	 */
2616
	public function setItemsDetailForm(callable $callable_set_container)
2617
	{
2618
		if ($this->items_detail instanceof Column\ItemDetail) {
2619
			$this->items_detail->setForm(
2620
				new Utils\ItemDetailForm($callable_set_container)
2621
			);
2622
2623
			return $this;
2624
		}
2625
2626
		throw new DataGridException('Please set the ItemDetail first.');
2627
	}
2628
2629
2630
	/**
2631
	 * @return Nette\Forms\Container|NULL
2632
	 */
2633
	public function getItemDetailForm()
2634
	{
2635
		if ($this->items_detail instanceof Column\ItemDetail) {
2636
			return $this->items_detail->getForm();
2637
		}
2638
2639
		return NULL;
2640
	}
2641
2642
2643
	/********************************************************************************
2644
	 *                                ROW PRIVILEGES                                *
2645
	 ********************************************************************************/
2646
2647
2648
	/**
2649
	 * @param  callable $condition
2650
	 * @return void
2651
	 */
2652
	public function allowRowsGroupAction(callable $condition)
2653
	{
2654
		$this->row_conditions['group_action'] = $condition;
2655
	}
2656
2657
2658
	/**
2659
	 * @param  callable $condition
2660
	 * @return void
2661
	 */
2662
	public function allowRowsInlineEdit(callable $condition)
2663
	{
2664
		$this->row_conditions['inline_edit'] = $condition;
2665
	}
2666
2667
2668
	/**
2669
	 * @param  string   $key
2670
	 * @param  callable $condition
2671
	 * @return void
2672
	 */
2673
	public function allowRowsAction($key, callable $condition)
2674
	{
2675
		$this->row_conditions['action'][$key] = $condition;
2676
	}
2677
2678
2679
	/**
2680
	 * @param  string      $name
2681
	 * @param  string|null $key
2682
	 * @return bool|callable
2683
	 */
2684
	public function getRowCondition($name, $key = NULL)
2685
	{
2686
		if (!isset($this->row_conditions[$name])) {
2687
			return FALSE;
2688
		}
2689
2690
		$condition = $this->row_conditions[$name];
2691
2692
		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...
2693
			return $condition;
2694
		}
2695
2696
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2697
	}
2698
2699
2700
	/********************************************************************************
2701
	 *                               COLUMN CALLBACK                                *
2702
	 ********************************************************************************/
2703
2704
2705
	/**
2706
	 * @param  string   $key
2707
	 * @param  callable $callback
2708
	 * @return void
2709
	 */
2710
	public function addColumnCallback($key, callable $callback)
2711
	{
2712
		$this->column_callbacks[$key] = $callback;
2713
	}
2714
2715
2716
	/**
2717
	 * @param  string $key
2718
	 * @return callable|null
2719
	 */
2720
	public function getColumnCallback($key)
2721
	{
2722
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2723
	}
2724
2725
2726
	/********************************************************************************
2727
	 *                                 INLINE EDIT                                  *
2728
	 ********************************************************************************/
2729
2730
2731
	/**
2732
	 * @return InlineEdit
2733
	 */
2734
	public function addInlineEdit($primary_where_column = NULL)
2735
	{
2736
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2737
2738
		return $this->inlineEdit;
2739
	}
2740
2741
2742
	/**
2743
	 * @return InlineEdit|null
2744
	 */
2745
	public function getInlineEdit()
2746
	{
2747
		return $this->inlineEdit;
2748
	}
2749
2750
2751
	/**
2752
	 * @param  mixed $id
2753
	 * @return void
2754
	 */
2755
	public function handleInlineEdit($id)
2756
	{
2757
		if ($this->inlineEdit) {
2758
			$this->inlineEdit->setItemId($id);
2759
2760
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2761
2762
			$this['filter']['inline_edit']->addHidden('_id', $id);
2763
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2764
2765
			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...
2766
				$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...
2767
			}
2768
2769
			$this->redrawItem($id, $primary_where_column);
2770
		}
2771
	}
2772
2773
2774
	/********************************************************************************
2775
	 *                                  INLINE ADD                                  *
2776
	 ********************************************************************************/
2777
2778
2779
	/**
2780
	 * @return InlineEdit
2781
	 */
2782
	public function addInlineAdd()
2783
	{
2784
		$this->inlineAdd = new InlineEdit($this);
2785
2786
		$this->inlineAdd
2787
			->setIcon('plus')
2788
			->setClass('btn btn-xs btn-default');
2789
2790
		return $this->inlineAdd;
2791
	}
2792
2793
2794
	/**
2795
	 * @return InlineEdit|null
2796
	 */
2797
	public function getInlineAdd()
2798
	{
2799
		return $this->inlineAdd;
2800
	}
2801
2802
2803
	/********************************************************************************
2804
	 *                               HIDEABLE COLUMNS                               *
2805
	 ********************************************************************************/
2806
2807
2808
	/**
2809
	 * Can datagrid hide colums?
2810
	 * @return boolean
2811
	 */
2812
	public function canHideColumns()
2813
	{
2814
		return (bool) $this->can_hide_columns;
2815
	}
2816
2817
2818
	/**
2819
	 * Order Grid to set columns hideable.
2820
	 * @return static
2821
	 */
2822
	public function setColumnsHideable()
2823
	{
2824
		$this->can_hide_columns = TRUE;
2825
2826
		return $this;
2827
	}
2828
2829
2830
	/********************************************************************************
2831
	 *                                COLUMNS SUMMARY                               *
2832
	 ********************************************************************************/
2833
2834
2835
	/**
2836
	 * Will datagrid show summary in the end?
2837
	 * @return bool
2838
	 */
2839
	public function hasColumnsSummary()
2840
	{
2841
		return $this->columnsSummary instanceof ColumnsSummary;
2842
	}
2843
2844
2845
	/**
2846
	 * Set columns to be summarized in the end.
2847
	 * @param  array  $columns
2848
	 * @return ColumnsSummary
2849
	 */
2850
	public function setColumnsSummary(array $columns)
2851
	{
2852
		$this->columnsSummary = new ColumnsSummary($this, $columns);
2853
2854
		return $this->columnsSummary;
2855
	}
2856
2857
2858
	/**
2859
	 * @return ColumnsSummary|NULL
2860
	 */
2861
	public function getColumnsSummary()
2862
	{
2863
		return $this->columnsSummary;
2864
	}
2865
2866
2867
	/********************************************************************************
2868
	 *                                   INTERNAL                                   *
2869
	 ********************************************************************************/
2870
2871
2872
	/**
2873
	 * Tell grid filters to by submitted automatically
2874
	 * @param bool $auto
2875
	 */
2876
	public function setAutoSubmit($auto = TRUE)
2877
	{
2878
		$this->auto_submit = (bool) $auto;
2879
2880
		return $this;
2881
	}
2882
2883
2884
	/**
2885
	 * @return bool
2886
	 */
2887
	public function hasAutoSubmit()
2888
	{
2889
		return $this->auto_submit;
2890
	}
2891
2892
2893
	/**
2894
	 * Submit button when no auto-submitting is used
2895
	 * @return Filter\SubmitButton
2896
	 */
2897
	public function getFilterSubmitButton()
2898
	{
2899
		if ($this->hasAutoSubmit()) {
2900
			throw new DataGridException(
2901
				'DataGrid has auto-submit. Turn it off before setting filter submit button.'
2902
			);
2903
		}
2904
2905
		if ($this->filter_submit_button === NULL) {
2906
			$this->filter_submit_button = new Filter\SubmitButton($this);
2907
		}
2908
2909
		return $this->filter_submit_button;
2910
	}
2911
2912
2913
	/********************************************************************************
2914
	 *                                   INTERNAL                                   *
2915
	 ********************************************************************************/
2916
2917
2918
	/**
2919
	 * Get count of columns
2920
	 * @return int
2921
	 */
2922
	public function getColumnsCount()
2923
	{
2924
		$count = sizeof($this->getColumns());
2925
2926
		if (!empty($this->actions)
2927
			|| $this->isSortable()
2928
			|| $this->getItemsDetail()
2929
			|| $this->getInlineEdit()
2930
			|| $this->getInlineAdd()) {
2931
			$count++;
2932
		}
2933
2934
		if ($this->hasGroupActions()) {
2935
			$count++;
2936
		}
2937
2938
		return $count;
2939
	}
2940
2941
2942
	/**
2943
	 * Get primary key of datagrid data source
2944
	 * @return string
2945
	 */
2946
	public function getPrimaryKey()
2947
	{
2948
		return $this->primary_key;
2949
	}
2950
2951
2952
	/**
2953
	 * Get set of set columns
2954
	 * @return Column\IColumn[]
2955
	 */
2956
	public function getColumns()
2957
	{
2958
		$return = $this->columns;
2959
2960
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2961
			$columns_to_hide = [];
2962
2963
			foreach ($this->columns as $key => $column) {
2964
				if ($column->getDefaultHide()) {
2965
					$columns_to_hide[] = $key;
2966
				}
2967
			}
2968
2969
			if (!empty($columns_to_hide)) {
2970
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2971
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2972
			}
2973
		}
2974
2975
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2976
2977
		foreach ($hidden_columns as $column) {
2978
			if (!empty($this->columns[$column])) {
2979
				$this->columns_visibility[$column] = [
2980
					'visible' => FALSE
2981
				];
2982
2983
				unset($return[$column]);
2984
			}
2985
		}
2986
2987
		return $return;
2988
	}
2989
2990
2991
	public function getColumnsVisibility()
2992
	{
2993
		$return = $this->columns_visibility;
2994
2995
		foreach ($this->columns_visibility as $key => $column) {
2996
			$return[$key]['column'] = $this->columns[$key];
2997
		}
2998
2999
		return $return;
3000
	}
3001
3002
3003
	/**
3004
	 * @return PresenterComponent
3005
	 */
3006
	public function getParent()
3007
	{
3008
		$parent = parent::getParent();
3009
3010
		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...
3011
			throw new DataGridHasToBeAttachedToPresenterComponentException(
3012
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
3013
			);
3014
		}
3015
3016
		return $parent;
3017
	}
3018
3019
3020
	/**
3021
	 * Some of datagrid columns is hidden by default
3022
	 * @param bool $default_hide
3023
	 */
3024
	public function setSomeColumnDefaultHide($default_hide)
3025
	{
3026
		$this->some_column_default_hide = $default_hide;
3027
	}
3028
3029
3030
	/**
3031
	 * Are some of columns hidden bydefault?
3032
	 */
3033
	public function hasSomeColumnDefaultHide()
3034
	{
3035
		return $this->some_column_default_hide;
3036
	}
3037
3038
}
3039