Completed
Pull Request — master (#228)
by Roman
02:55
created

DataGrid   F

Complexity

Total Complexity 314

Size/Duplication

Total Lines 2721
Duplicated Lines 3.82 %

Coupling/Cohesion

Components 5
Dependencies 43

Importance

Changes 44
Bugs 8 Features 14
Metric Value
wmc 314
c 44
b 8
f 14
lcom 5
cbo 43
dl 104
loc 2721
rs 0.5217

120 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 21 1
A attached() 0 13 3
B render() 0 51 4
C getRows() 0 40 11
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 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 11 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() 0 12 3
A addFilterDate() 0 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 isFilterActive() 0 6 2
A setFilterActive() 0 6 1
A setFilter() 0 8 1
D setDefaultFilter() 0 38 9
B findDefaultFilter() 0 18 5
B createComponentFilter() 15 68 6
D filterSucceeded() 14 95 24
A setOuterFilterRendering() 0 6 1
A hasOuterFilterRendering() 0 4 1
C findSessionValues() 0 50 17
A addExportCallback() 0 8 2
A addExportCsv() 0 10 1
A addExportCsvFiltered() 0 10 1
A addToExports() 0 8 2
A resetExportsLinks() 0 6 2
A addGroupAction() 0 4 1
A addGroupSelectAction() 0 4 1
A addGroupTextAction() 0 4 1
A getGroupActionCollection() 0 8 2
A hasGroupActions() 0 4 1
A handlePage() 0 10 1
B handleSort() 0 39 5
A handleResetFilter() 0 21 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 11 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 createComponentPaginator() 0 15 1
B createComponentPerPage() 0 26 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 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 13 2
A addInlineAdd() 0 10 1
A getInlineAdd() 0 4 1
A canHideColumns() 0 4 1
A setColumnsHideable() 0 6 1
B getColumnsCount() 0 18 7
A getPrimaryKey() 0 4 1
C getColumns() 0 32 7
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
21
/**
22
 * @method onRedraw()
23
 * @method onRender()
24
 * @method onColumnAdd()
25
 */
26
class DataGrid extends Nette\Application\UI\Control
27
{
28
29
	/**
30
	 * @var callable[]
31
	 */
32
	public $onRedraw;
33
34
	/**
35
	 * @var callable[]
36
	 */
37
	public $onRender = [];
38
39
	/**
40
	 * @var callable[]
41
	 */
42
	public $onColumnAdd;
43
44
	/**
45
	 * @var string
46
	 */
47
	public static $icon_prefix = 'fa fa-';
48
49
	/**
50
	 * When set to TRUE, datagrid throws an exception
51
	 * 	when tring to get related entity within join and entity does not exist
52
	 * @var bool
53
	 */
54
	public $strict_entity_property = FALSE;
55
56
	/**
57
	 * @var int
58
	 * @persistent
59
	 */
60
	public $page = 1;
61
62
	/**
63
	 * @var int|string
64
	 * @persistent
65
	 */
66
	public $per_page;
67
68
	/**
69
	 * @var array
70
	 * @persistent
71
	 */
72
	public $sort = [];
73
74
	/**
75
	 * @var array
76
	 */
77
	public $default_sort = [];
78
79
	/**
80
	 * @var array
81
	 */
82
	public $default_filter = [];
83
84
	/**
85
	 * @var bool
86
	 */
87
	public $default_filter_use_on_reset = TRUE;
88
89
	/**
90
	 * @var array
91
	 * @persistent
92
	 */
93
	public $filter = [];
94
95
	/**
96
	 * @var callable|null
97
	 */
98
	protected $sort_callback = NULL;
99
100
	/**
101
	 * @var bool
102
	 */
103
	protected $use_happy_components = TRUE;
104
105
	/**
106
	 * @var callable
107
	 */
108
	protected $rowCallback;
109
110
	/**
111
	 * @var array
112
	 */
113
	protected $items_per_page_list;
114
115
	/**
116
	 * @var string
117
	 */
118
	protected $template_file;
119
120
	/**
121
	 * @var Column\IColumn[]
122
	 */
123
	protected $columns = [];
124
125
	/**
126
	 * @var Column\Action[]
127
	 */
128
	protected $actions = [];
129
130
	/**
131
	 * @var GroupAction\GroupActionCollection
132
	 */
133
	protected $group_action_collection;
134
135
	/**
136
	 * @var Filter\Filter[]
137
	 */
138
	protected $filters = [];
139
140
	/**
141
	 * @var Export\Export[]
142
	 */
143
	protected $exports = [];
144
145
	/**
146
	 * @var DataModel
147
	 */
148
	protected $dataModel;
149
150
	/**
151
	 * @var DataFilter
152
	 */
153
	protected $dataFilter;
154
155
	/**
156
	 * @var string
157
	 */
158
	protected $primary_key = 'id';
159
160
	/**
161
	 * @var bool
162
	 */
163
	protected $do_paginate = TRUE;
164
165
	/**
166
	 * @var bool
167
	 */
168
	protected $csv_export = TRUE;
169
170
	/**
171
	 * @var bool
172
	 */
173
	protected $csv_export_filtered = TRUE;
174
175
	/**
176
	 * @var bool
177
	 */
178
	protected $sortable = FALSE;
179
180
	/**
181
	 * @var string
182
	 */
183
	protected $sortable_handler = 'sort!';
184
185
	/**
186
	 * @var string
187
	 */
188
	protected $original_template;
189
190
	/**
191
	 * @var array
192
	 */
193
	protected $redraw_item;
194
195
	/**
196
	 * @var mixed
197
	 */
198
	protected $translator;
199
200
	/**
201
	 * @var bool
202
	 */
203
	protected $force_filter_active;
204
205
	/**
206
	 * @var callable
207
	 */
208
	protected $tree_view_children_callback;
209
210
	/**
211
	 * @var callable
212
	 */
213
	protected $tree_view_has_children_callback;
214
215
	/**
216
	 * @var string
217
	 */
218
	protected $tree_view_has_children_column;
219
220
	/**
221
	 * @var bool
222
	 */
223
	protected $outer_filter_rendering = FALSE;
224
225
	/**
226
	 * @var array
227
	 */
228
	protected $columns_export_order = [];
229
230
	/**
231
	 * @var bool
232
	 */
233
	protected $remember_state = TRUE;
234
235
	/**
236
	 * @var bool
237
	 */
238
	protected $refresh_url = TRUE;
239
240
	/**
241
	 * @var Nette\Http\SessionSection
242
	 */
243
	protected $grid_session;
244
245
	/**
246
	 * @var Column\ItemDetail
247
	 */
248
	protected $items_detail;
249
250
	/**
251
	 * @var array
252
	 */
253
	protected $row_conditions = [
254
		'group_action' => FALSE,
255
		'action' => []
256
	];
257
258
	/**
259
	 * @var array
260
	 */
261
	protected $column_callbacks = [];
262
263
	/**
264
	 * @var bool
265
	 */
266
	protected $can_hide_columns = FALSE;
267
268
	/**
269
	 * @var array
270
	 */
271
	protected $columns_visibility = [];
272
273
	/**
274
	 * @var InlineEdit
275
	 */
276
	protected $inlineEdit;
277
278
	/**
279
	 * @var InlineEdit
280
	 */
281
	protected $inlineAdd;
282
283
	/**
284
	 * @var bool
285
	 */
286
	protected $snippets_set = FALSE;
287
288
	/**
289
	 * @var bool
290
	 */
291
	protected $some_column_default_hide = FALSE;
292
293
	/**
294
	 * @var array|NULL
295
	**/
296
	protected $rows;
297
298
299
	/**
300
	 * @param Nette\ComponentModel\IContainer|NULL $parent
301
	 * @param string                               $name
302
	 */
303
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
304
	{
305
		parent::__construct($parent, $name);
306
307
		$this->monitor('Nette\Application\UI\Presenter');
308
309
		/**
310
		 * Try to find previous filters, pagination, per_page and other values in session
311
		 */
312
		$this->onRender[] = [$this, 'findSessionValues'];
313
314
		/**
315
		 * Find default filter values
316
		 */
317
		$this->onRender[] = [$this, 'findDefaultFilter'];
318
319
		/**
320
		 * Find default sort
321
		 */
322
		$this->onRender[] = [$this, 'findDefaultSort'];
323
	}
324
325
326
	/**
327
	 * {inheritDoc}
328
	 * @return void
329
	 */
330
	public function attached($presenter)
331
	{
332
		parent::attached($presenter);
333
334
		if ($presenter instanceof Nette\Application\UI\Presenter) {
335
			/**
336
			 * Get session
337
			 */
338
			if ($this->remember_state) {
339
				$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...
340
			}
341
		}
342
	}
343
344
345
	/********************************************************************************
346
	 *                                  RENDERING                                   *
347
	 ********************************************************************************/
348
349
350
	/**
351
	 * Render template
352
	 * @return void
353
	 */
354
	public function render()
355
	{
356
		/**
357
		 * Check whether datagrid has set some columns, initiated data source, etc
358
		 */
359
		if (!($this->dataModel instanceof DataModel)) {
360
			throw new DataGridException('You have to set a data source first.');
361
		}
362
363
		if (empty($this->columns)) {
364
			throw new DataGridException('You have to add at least one column.');
365
		}
366
367
		$this->getTemplate()->setTranslator($this->getTranslator());
368
369
		/**
370
		 * Invoke possible events
371
		 */
372
		$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...
373
374
		if ($this->isTreeView()) {
375
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
376
		}
377
378
		$this->getTemplate()->add('rows', $this->getRows(TRUE));
379
380
		$this->getTemplate()->add('columns', $this->getColumns());
381
		$this->getTemplate()->add('actions', $this->actions);
382
		$this->getTemplate()->add('exports', $this->exports);
383
		$this->getTemplate()->add('filters', $this->filters);
384
385
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
386
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
387
		$this->getTemplate()->add('icon_prefix', static::$icon_prefix);
388
		$this->getTemplate()->add('items_detail', $this->items_detail);
389
		$this->getTemplate()->add('columns_visibility', $this->columns_visibility);
390
391
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
392
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
393
394
		/**
395
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
396
		 */
397
		$this->getTemplate()->add('filter', $this['filter']);
398
399
		/**
400
		 * Set template file and render it
401
		 */
402
		$this->getTemplate()->setFile($this->getTemplateFile());
403
		$this->getTemplate()->render();
404
	}
405
406
407
	/**
408
	 * Get all prepared Rows
409
	 * @return array
410
	 */
411
	public function getRows($refresh = FALSE)
412
	{
413
		if (is_array($this->rows) && !$refresh)
414
			return $this->rows;
415
		/**
416
		 * Prepare data for rendering (datagrid may render just one item)
417
		 */
418
		$this->rows = [];
419
420
421
		if (!empty($this->redraw_item)) {
422
			$items = $this->dataModel->filterRow($this->redraw_item);
423
		} else {
424
			if (empty($this->sort_callback) && !empty($this->sort)) {
425
				$column = $this->getColumn(array_keys($this->sort)[0]);
426
				if ($column->isSortable() AND Nette\Utils\Validators::isCallable($column->getSortableCallback()))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
427
					$this->sort_callback = $column->getSortableCallback();
428
			}
429
430
			$items = Nette\Utils\Callback::invokeArgs(
431
				[$this->dataModel, 'filterData'],
432
				[
433
					$this->getPaginator(),
434
					new Sorting($this->sort, $this->sort_callback),
435
					$this->assableFilters()
436
				]
437
			);
438
		}
439
440
		$callback = $this->rowCallback ?: NULL;
441
442
		foreach ($items as $item) {
443
			$this->rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
444
445
			if ($callback) {
446
				$callback($item, $row->getControl());
447
			}
448
		}
449
		return $this->rows;
450
	}
451
452
453
	/********************************************************************************
454
	 *                                 ROW CALLBACK                                 *
455
	 ********************************************************************************/
456
457
458
	/**
459
	 * Each row can be modified with user callback
460
	 * @param  callable  $callback
461
	 * @return static
462
	 */
463
	public function setRowCallback(callable $callback)
464
	{
465
		$this->rowCallback = $callback;
466
467
		return $this;
468
	}
469
470
471
	/********************************************************************************
472
	 *                                 DATA SOURCE                                  *
473
	 ********************************************************************************/
474
475
476
	/**
477
	 * By default ID, you can change that
478
	 * @param string $primary_key
479
	 * @return static
480
	 */
481
	public function setPrimaryKey($primary_key)
482
	{
483
		if ($this->dataModel instanceof DataModel) {
484
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
485
		}
486
487
		$this->primary_key = $primary_key;
488
489
		return $this;
490
	}
491
492
493
	/**
494
	 * Set Grid data source
495
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
496
	 * @return static
497
	 */
498
	public function setDataSource($source)
499
	{
500
		$this->dataModel = new DataModel($source, $this->primary_key);
501
502
		return $this;
503
	}
504
505
506
	/********************************************************************************
507
	 *                                  TEMPLATING                                  *
508
	 ********************************************************************************/
509
510
511
	/**
512
	 * Set custom template file to render
513
	 * @param string $template_file
514
	 * @return static
515
	 */
516
	public function setTemplateFile($template_file)
517
	{
518
		$this->template_file = $template_file;
519
520
		return $this;
521
	}
522
523
524
	/**
525
	 * Get DataGrid template file
526
	 * @return string
527
	 * @return static
528
	 */
529
	public function getTemplateFile()
530
	{
531
		return $this->template_file ?: $this->getOriginalTemplateFile();
532
	}
533
534
535
	/**
536
	 * Get DataGrid original template file
537
	 * @return string
538
	 */
539
	public function getOriginalTemplateFile()
540
	{
541
		return __DIR__.'/templates/datagrid.latte';
542
	}
543
544
545
	/**
546
	 * Tell datagrid wheteher to use or not happy components
547
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
548
	 * @return void|bool
549
	 */
550
	public function useHappyComponents($use = NULL)
551
	{
552
		if (NULL === $use) {
553
			return $this->use_happy_components;
554
		}
555
556
		$this->use_happy_components = (bool) $use;
557
	}
558
559
560
	/********************************************************************************
561
	 *                                   SORTING                                    *
562
	 ********************************************************************************/
563
564
565
	/**
566
	 * Set default sorting
567
	 * @param array $sort
568
	 * @return static
569
	 */
570
	public function setDefaultSort($sort)
571
	{
572
		if (is_string($sort)) {
573
			$sort = [$sort => 'ASC'];
574
		} else {
575
			$sort = (array) $sort;
576
		}
577
578
		$this->default_sort = $sort;
579
580
		return $this;
581
	}
582
583
584
	/**
585
	 * User may set default sorting, apply it
586
	 * @return void
587
	 */
588
	public function findDefaultSort()
589
	{
590
		if (!empty($this->sort)) {
591
			return;
592
		}
593
594
		if (!empty($this->default_sort)) {
595
			$this->sort = $this->default_sort;
596
		}
597
598
		$this->saveSessionData('_grid_sort', $this->sort);
599
	}
600
601
602
	/**
603
	 * Set grido to be sortable
604
	 * @param bool $sortable
605
	 * @return static
606
	 */
607
	public function setSortable($sortable = TRUE)
608
	{
609
		if ($this->getItemsDetail()) {
610
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
611
		}
612
613
		$this->sortable = (bool) $sortable;
614
615
		return $this;
616
	}
617
618
619
	/**
620
	 * Set sortable handle
621
	 * @param string $handler
622
	 * @return static
623
	 */
624
	public function setSortableHandler($handler = 'sort!')
625
	{
626
		$this->sortable_handler = (string) $handler;
627
628
		return $this;
629
	}
630
631
632
	/**
633
	 * Tell whether DataGrid is sortable
634
	 * @return bool
635
	 */
636
	public function isSortable()
637
	{
638
		return $this->sortable;
639
	}
640
641
	/**
642
	 * Return sortable handle name
643
	 * @return string
644
	 */
645
	public function getSortableHandler()
646
	{
647
		return $this->sortable_handler;
648
	}
649
650
651
	/********************************************************************************
652
	 *                                  TREE VIEW                                   *
653
	 ********************************************************************************/
654
655
656
	/**
657
	 * Is tree view set?
658
	 * @return boolean
659
	 */
660
	public function isTreeView()
661
	{
662
		return (bool) $this->tree_view_children_callback;
663
	}
664
665
666
	/**
667
	 * Setting tree view
668
	 * @param callable $get_children_callback
669
	 * @param string|callable $tree_view_has_children_column
670
	 * @return static
671
	 */
672
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
673
	{
674
		if (!is_callable($get_children_callback)) {
675
			throw new DataGridException(
676
				'Parameters to method DataGrid::setTreeView must be of type callable'
677
			);
678
		}
679
680
		if (is_callable($tree_view_has_children_column)) {
681
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
682
			$tree_view_has_children_column = NULL;
683
		}
684
685
		$this->tree_view_children_callback = $get_children_callback;
686
		$this->tree_view_has_children_column = $tree_view_has_children_column;
687
688
		/**
689
		 * TUrn off pagination
690
		 */
691
		$this->setPagination(FALSE);
692
693
		/**
694
		 * Set tree view template file
695
		 */
696
		if (!$this->template_file) {
697
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
698
		}
699
700
		return $this;
701
	}
702
703
704
	/**
705
	 * Is tree view children callback set?
706
	 * @return boolean
707
	 */
708
	public function hasTreeViewChildrenCallback()
709
	{
710
		return is_callable($this->tree_view_has_children_callback);
711
	}
712
713
714
	/**
715
	 * @param  mixed $item
716
	 * @return boolean
717
	 */
718
	public function treeViewChildrenCallback($item)
719
	{
720
		return call_user_func($this->tree_view_has_children_callback, $item);
721
	}
722
723
724
	/********************************************************************************
725
	 *                                    COLUMNS                                   *
726
	 ********************************************************************************/
727
728
729
	/**
730
	 * Add text column with no other formating
731
	 * @param  string      $key
732
	 * @param  string      $name
733
	 * @param  string|null $column
734
	 * @return Column\ColumnText
735
	 */
736
	public function addColumnText($key, $name, $column = NULL)
737
	{
738
		$this->addColumnCheck($key);
739
		$column = $column ?: $key;
740
741
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
742
	}
743
744
745
	/**
746
	 * Add column with link
747
	 * @param  string      $key
748
	 * @param  string      $name
749
	 * @param  string|null $column
750
	 * @return Column\ColumnLink
751
	 */
752
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
753
	{
754
		$this->addColumnCheck($key);
755
		$column = $column ?: $key;
756
		$href = $href ?: $key;
757
758
		if (NULL === $params) {
759
			$params = [$this->primary_key];
760
		}
761
762
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
763
	}
764
765
766
	/**
767
	 * Add column with possible number formating
768
	 * @param  string      $key
769
	 * @param  string      $name
770
	 * @param  string|null $column
771
	 * @return Column\ColumnNumber
772
	 */
773
	public function addColumnNumber($key, $name, $column = NULL)
774
	{
775
		$this->addColumnCheck($key);
776
		$column = $column ?: $key;
777
778
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
779
	}
780
781
782
	/**
783
	 * Add column with date formating
784
	 * @param  string      $key
785
	 * @param  string      $name
786
	 * @param  string|null $column
787
	 * @return Column\ColumnDateTime
788
	 */
789
	public function addColumnDateTime($key, $name, $column = NULL)
790
	{
791
		$this->addColumnCheck($key);
792
		$column = $column ?: $key;
793
794
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
795
	}
796
797
798
	/**
799
	 * Add column status
800
	 * @param  string      $key
801
	 * @param  string      $name
802
	 * @param  string|null $column
803
	 * @return Column\ColumnStatus
804
	 */
805
	public function addColumnStatus($key, $name, $column = NULL)
806
	{
807
		$this->addColumnCheck($key);
808
		$column = $column ?: $key;
809
810
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
811
	}
812
813
814
	/**
815
	 * @param string $key
816
	 * @param Column\Column $column
817
	 * @return Column\Column
818
	 */
819
	protected function addColumn($key, Column\Column $column)
820
	{
821
		$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...
822
823
		$this->columns_visibility[$key] = [
824
			'visible' => TRUE,
825
			'name' => $column->getName()
826
		];
827
828
		return $this->columns[$key] = $column;
829
	}
830
831
832
	/**
833
	 * Return existing column
834
	 * @param  string $key
835
	 * @return Column\Column
836
	 * @throws DataGridException
837
	 */
838
	public function getColumn($key)
839
	{
840
		if (!isset($this->columns[$key])) {
841
			throw new DataGridException("There is no column at key [$key] defined.");
842
		}
843
844
		return $this->columns[$key];
845
	}
846
847
848
	/**
849
	 * Remove column
850
	 * @param string $key
851
	 * @return void
852
	 */
853
	public function removeColumn($key)
854
	{
855
		unset($this->columns[$key]);
856
	}
857
858
859
	/**
860
	 * Check whether given key already exists in $this->columns
861
	 * @param  string $key
862
	 * @throws DataGridException
863
	 */
864
	protected function addColumnCheck($key)
865
	{
866
		if (isset($this->columns[$key])) {
867
			throw new DataGridException("There is already column at key [$key] defined.");
868
		}
869
	}
870
871
872
	/********************************************************************************
873
	 *                                    ACTIONS                                   *
874
	 ********************************************************************************/
875
876
877
	/**
878
	 * Create action
879
	 * @param string     $key
880
	 * @param string     $name
881
	 * @param string     $href
882
	 * @param array|null $params
883
	 * @return Column\Action
884
	 */
885
	public function addAction($key, $name, $href = NULL, array $params = NULL)
886
	{
887
		$this->addActionCheck($key);
888
		$href = $href ?: $key;
889
890
		if (NULL === $params) {
891
			$params = [$this->primary_key];
892
		}
893
894
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
895
	}
896
897
898
	/**
899
	 * Create action callback
900
	 * @param string     $key
901
	 * @param string     $name
902
	 * @return Column\Action
903
	 */
904
	public function addActionCallback($key, $name, $callback = NULL)
905
	{
906
		$this->addActionCheck($key);
907
		$params = ['__id' => $this->primary_key];
908
909
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
910
911
		if ($callback) {
912
			if (!is_callable($callback)) {
913
				throw new DataGridException('ActionCallback callback has to be callable.');
914
			}
915
916
			$action->onClick[] = $callback;
917
		}
918
919
		return $action;
920
	}
921
922
923
	/**
924
	 * Get existing action
925
	 * @param  string       $key
926
	 * @return Column\Action
927
	 * @throws DataGridException
928
	 */
929
	public function getAction($key)
930
	{
931
		if (!isset($this->actions[$key])) {
932
			throw new DataGridException("There is no action at key [$key] defined.");
933
		}
934
935
		return $this->actions[$key];
936
	}
937
938
939
	/**
940
	 * Remove action
941
	 * @param string $key
942
	 * @return void
943
	 */
944
	public function removeAction($key)
945
	{
946
		unset($this->actions[$key]);
947
	}
948
949
950
	/**
951
	 * Check whether given key already exists in $this->filters
952
	 * @param  string $key
953
	 * @throws DataGridException
954
	 */
955
	protected function addActionCheck($key)
956
	{
957
		if (isset($this->actions[$key])) {
958
			throw new DataGridException("There is already action at key [$key] defined.");
959
		}
960
	}
961
962
963
	/********************************************************************************
964
	 *                                    FILTERS                                   *
965
	 ********************************************************************************/
966
967
968
	/**
969
	 * Add filter fot text search
970
	 * @param string       $key
971
	 * @param string       $name
972
	 * @param array|string $columns
973
	 * @return Filter\FilterText
974
	 * @throws DataGridException
975
	 */
976
	public function addFilterText($key, $name, $columns = NULL)
977
	{
978
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
979
980
		if (!is_array($columns)) {
981
			throw new DataGridException("Filter Text can except only array or string.");
982
		}
983
984
		$this->addFilterCheck($key);
985
986
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
987
	}
988
989
990
	/**
991
	 * Add select box filter
992
	 * @param string $key
993
	 * @param string $name
994
	 * @param array  $options
995
	 * @param string $column
996
	 * @return Filter\FilterSelect
997
	 * @throws DataGridException
998
	 */
999 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...
1000
	{
1001
		$column = $column ?: $key;
1002
1003
		if (!is_string($column)) {
1004
			throw new DataGridException("Filter Select can only filter in one column.");
1005
		}
1006
1007
		$this->addFilterCheck($key);
1008
1009
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
1010
	}
1011
1012
1013
	/**
1014
	 * Add multi select box filter
1015
	 * @param string $key
1016
	 * @param string $name
1017
	 * @param array  $options
1018
	 * @param string $column
1019
	 * @return Filter\FilterSelect
1020
	 * @throws DataGridException
1021
	 */
1022
	public function addFilterMultiSelect($key, $name, array $options, $column = NULL)
1023
	{
1024
		$column = $column ?: $key;
1025
1026
		if (!is_string($column)) {
1027
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1028
		}
1029
1030
		$this->addFilterCheck($key);
1031
1032
		return $this->filters[$key] = new Filter\FilterMultiSelect($key, $name, $options, $column);
1033
	}
1034
1035
1036
	/**
1037
	 * Add datepicker filter
1038
	 * @param string $key
1039
	 * @param string $name
1040
	 * @param string $column
1041
	 * @return Filter\FilterDate
1042
	 * @throws DataGridException
1043
	 */
1044
	public function addFilterDate($key, $name, $column = NULL)
1045
	{
1046
		$column = $column ?: $key;
1047
1048
		if (!is_string($column)) {
1049
			throw new DataGridException("FilterDate can only filter in one column.");
1050
		}
1051
1052
		$this->addFilterCheck($key);
1053
1054
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
1055
	}
1056
1057
1058
	/**
1059
	 * Add range filter (from - to)
1060
	 * @param string $key
1061
	 * @param string $name
1062
	 * @param string $column
1063
	 * @return Filter\FilterRange
1064
	 * @throws DataGridException
1065
	 */
1066 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...
1067
	{
1068
		$column = $column ?: $key;
1069
1070
		if (!is_string($column)) {
1071
			throw new DataGridException("FilterRange can only filter in one column.");
1072
		}
1073
1074
		$this->addFilterCheck($key);
1075
1076
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
1077
	}
1078
1079
1080
	/**
1081
	 * Add datepicker filter (from - to)
1082
	 * @param string $key
1083
	 * @param string $name
1084
	 * @param string $column
1085
	 * @return Filter\FilterDateRange
1086
	 * @throws DataGridException
1087
	 */
1088 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...
1089
	{
1090
		$column = $column ?: $key;
1091
1092
		if (!is_string($column)) {
1093
			throw new DataGridException("FilterDateRange can only filter in one column.");
1094
		}
1095
1096
		$this->addFilterCheck($key);
1097
1098
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
1099
	}
1100
1101
1102
	/**
1103
	 * Check whether given key already exists in $this->filters
1104
	 * @param  string $key
1105
	 * @throws DataGridException
1106
	 */
1107
	protected function addFilterCheck($key)
1108
	{
1109
		if (isset($this->filters[$key])) {
1110
			throw new DataGridException("There is already action at key [$key] defined.");
1111
		}
1112
	}
1113
1114
1115
	/**
1116
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1117
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1118
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1119
	 */
1120
	public function assableFilters()
1121
	{
1122
		foreach ($this->filter as $key => $value) {
1123
			if (!isset($this->filters[$key])) {
1124
				$this->deleteSesssionData($key);
1125
1126
				continue;
1127
			}
1128
1129
			if (is_array($value) || $value instanceof \Traversable) {
1130
				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...
1131
					$this->filters[$key]->setValue($value);
1132
				}
1133
			} else {
1134
				if ($value !== '' && $value !== NULL) {
1135
					$this->filters[$key]->setValue($value);
1136
				}
1137
			}
1138
		}
1139
1140
		foreach ($this->columns as $column) {
1141
			if (isset($this->sort[$column->getSortingColumn()])) {
1142
				$column->setSort($this->sort);
1143
			}
1144
		}
1145
1146
		return $this->filters;
1147
	}
1148
1149
1150
	/**
1151
	 * Remove filter
1152
	 * @param string $key
1153
	 * @return void
1154
	 */
1155
	public function removeFilter($key)
1156
	{
1157
		unset($this->filters[$key]);
1158
	}
1159
1160
1161
	/**
1162
	 * Get defined filter
1163
	 * @param  string $key
1164
	 * @return Filter\Filter
1165
	 */
1166
	public function getFilter($key)
1167
	{
1168
		if (!isset($this->filters[$key])) {
1169
			throw new DataGridException("Filter [{$key}] is not defined");
1170
		}
1171
1172
		return $this->filters[$key];
1173
	}
1174
1175
1176
	/********************************************************************************
1177
	 *                                  FILTERING                                   *
1178
	 ********************************************************************************/
1179
1180
1181
	/**
1182
	 * Is filter active?
1183
	 * @return boolean
1184
	 */
1185
	public function isFilterActive()
1186
	{
1187
		$is_filter = ArraysHelper::testTruthy($this->filter);
1188
1189
		return ($is_filter) || $this->force_filter_active;
1190
	}
1191
1192
1193
	/**
1194
	 * Tell that filter is active from whatever reasons
1195
	 * return static
1196
	 */
1197
	public function setFilterActive()
1198
	{
1199
		$this->force_filter_active = TRUE;
1200
1201
		return $this;
1202
	}
1203
1204
1205
	/**
1206
	 * Set filter values (force - overwrite user data)
1207
	 * @param array $filter
1208
	 * @return static
1209
	 */
1210
	public function setFilter(array $filter)
1211
	{
1212
		$this->filter = $filter;
1213
1214
		$this->saveSessionData('_grid_has_filtered', 1);
1215
1216
		return $this;
1217
	}
1218
1219
1220
	/**
1221
	 * If we want to sent some initial filter
1222
	 * @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...
1223
	 * @param bool  $use_on_reset
1224
	 * @return static
1225
	 */
1226
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1227
	{
1228
		foreach ($default_filter as $key => $value) {
1229
			$filter = $this->getFilter($key);
1230
1231
			if (!$filter) {
1232
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1233
			}
1234
1235
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1236
				throw new DataGridException(
1237
					"Default value of filter [$key] - MultiSelect has to be an array"
1238
				);
1239
			}
1240
1241
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1242
				if (!is_array($value)) {
1243
					throw new DataGridException(
1244
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1245
					);
1246
				}
1247
1248
				$temp = $value;
1249
				unset($temp['from'], $temp['to']);
1250
1251
				if (!empty($temp)) {
1252
					throw new DataGridException(
1253
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1254
					);
1255
				}
1256
			}
1257
		}
1258
1259
		$this->default_filter = $default_filter;
1260
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1261
1262
		return $this;
1263
	}
1264
1265
1266
	/**
1267
	 * User may set default filter, find it
1268
	 * @return void
1269
	 */
1270
	public function findDefaultFilter()
1271
	{
1272
		if (!empty($this->filter)) {
1273
			return;
1274
		}
1275
1276
		if ($this->getSessionData('_grid_has_filtered')) {
1277
			return;
1278
		}
1279
1280
		if (!empty($this->default_filter)) {
1281
			$this->filter = $this->default_filter;
1282
		}
1283
1284
		foreach ($this->filter as $key => $value) {
1285
			$this->saveSessionData($key, $value);
1286
		}
1287
	}
1288
1289
1290
	/**
1291
	 * FilterAndGroupAction form factory
1292
	 * @return Form
1293
	 */
1294
	public function createComponentFilter()
1295
	{
1296
		$form = new Form($this, 'filter');
1297
1298
		$form->setMethod('get');
1299
1300
		$form->setTranslator($this->getTranslator());
1301
1302
		/**
1303
		 * InlineEdit part
1304
		 */
1305
		$inline_edit_container = $form->addContainer('inline_edit');
1306
1307 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...
1308
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1309
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1310
				->setValidationScope(FALSE);
1311
1312
			$this->inlineEdit->onControlAdd($inline_edit_container);
1313
		}
1314
1315
		/**
1316
		 * InlineAdd part
1317
		 */
1318
		$inline_add_container = $form->addContainer('inline_add');
1319
1320 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...
1321
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1322
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1323
				->setValidationScope(FALSE)
1324
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1325
1326
			$this->inlineAdd->onControlAdd($inline_add_container);
1327
		}
1328
1329
		/**
1330
		 * ItemDetail form part
1331
		 */
1332
		$items_detail_form = $this->getItemDetailForm();
1333
1334
		if ($items_detail_form instanceof Nette\Forms\Container) {
1335
			$form['items_detail_form'] = $items_detail_form;
1336
		}
1337
1338
		/**
1339
		 * Filter part
1340
		 */
1341
		$filter_container = $form->addContainer('filter');
1342
1343
		foreach ($this->filters as $filter) {
1344
			$filter->addToFormContainer($filter_container);
1345
		}
1346
1347
		/**
1348
		 * Group action part
1349
		 */
1350
		$group_action_container = $form->addContainer('group_action');
1351
1352
		if ($this->hasGroupActions()) {
1353
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1354
		}
1355
1356
		$form->setDefaults(['filter' => $this->filter]);
1357
1358
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1359
1360
		return $form;
1361
	}
1362
1363
1364
	/**
1365
	 * Set $this->filter values after filter form submitted
1366
	 * @param  Form $form
1367
	 * @return void
1368
	 */
1369
	public function filterSucceeded(Form $form)
1370
	{
1371
		if ($this->snippets_set) {
1372
			return;
1373
		}
1374
1375
		$values = $form->getValues();
1376
1377
		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...
1378
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1379
				return;
1380
			}
1381
		}
1382
1383
		/**
1384
		 * Inline edit
1385
		 */
1386
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1387
			$edit = $form['inline_edit'];
1388
1389
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1390
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1391
				$primary_where_column = $form->getHttpData(
1392
					Form::DATA_LINE,
1393
					'inline_edit[_primary_where_column]'
1394
				);
1395
1396 View Code Duplication
				if ($edit['submit']->isSubmittedBy()) {
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...
1397
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1398
1399
					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...
1400
						$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...
1401
					}
1402
				}
1403
1404
				$this->redrawItem($id, $primary_where_column);
1405
1406
				return;
1407
			}
1408
		}
1409
1410
		/**
1411
		 * Inline add
1412
		 */
1413
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1414
			$add = $form['inline_add'];
1415
1416
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1417 View Code Duplication
				if ($add['submit']->isSubmittedBy()) {
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...
1418
					$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...
1419
1420
					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...
1421
						$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...
1422
					}
1423
				}
1424
1425
				return;
1426
			}
1427
		}
1428
1429
		/**
1430
		 * Filter itself
1431
		 */
1432
		$values = $values['filter'];
1433
1434
		foreach ($values as $key => $value) {
1435
			/**
1436
			 * Session stuff
1437
			 */
1438
			$this->saveSessionData($key, $value);
1439
1440
			/**
1441
			 * Other stuff
1442
			 */
1443
			$this->filter[$key] = $value;
1444
		}
1445
1446
		if (!empty($values)) {
1447
			$this->saveSessionData('_grid_has_filtered', 1);
1448
		}
1449
1450
		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...
1451
			$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...
1452
1453
			foreach ($this->columns as $key => $column) {
1454
				if ($column->isSortable()) {
1455
					$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...
1456
						'sort' => $column->getSortNext()
1457
					]);
1458
				}
1459
			}
1460
		}
1461
1462
		$this->reload();
1463
	}
1464
1465
1466
	/**
1467
	 * Should be datagrid filters rendered separately?
1468
	 * @param boolean $out
1469
	 * @return static
1470
	 */
1471
	public function setOuterFilterRendering($out = TRUE)
1472
	{
1473
		$this->outer_filter_rendering = (bool) $out;
1474
1475
		return $this;
1476
	}
1477
1478
1479
	/**
1480
	 * Are datagrid filters rendered separately?
1481
	 * @return boolean
1482
	 */
1483
	public function hasOuterFilterRendering()
1484
	{
1485
		return $this->outer_filter_rendering;
1486
	}
1487
1488
1489
	/**
1490
	 * Try to restore session stuff
1491
	 * @return void
1492
	 */
1493
	public function findSessionValues()
1494
	{
1495
		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...
1496
			return;
1497
		}
1498
1499
		if (!$this->remember_state) {
1500
			return;
1501
		}
1502
1503
		if ($page = $this->getSessionData('_grid_page')) {
1504
			$this->page = $page;
1505
		}
1506
1507
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1508
			$this->per_page = $per_page;
1509
		}
1510
1511
		if ($sort = $this->getSessionData('_grid_sort')) {
1512
			$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...
1513
		}
1514
1515
		foreach ($this->getSessionData() as $key => $value) {
1516
			$other_session_keys = [
1517
				'_grid_per_page',
1518
				'_grid_sort',
1519
				'_grid_page',
1520
				'_grid_has_filtered',
1521
				'_grid_hidden_columns',
1522
				'_grid_hidden_columns_manipulated'
1523
			];
1524
1525
			if (!in_array($key, $other_session_keys)) {
1526
				$this->filter[$key] = $value;
1527
			}
1528
		}
1529
1530
		/**
1531
		 * When column is sorted via custom callback, apply it
1532
		 */
1533
		if (empty($this->sort_callback) && !empty($this->sort)) {
1534
			foreach ($this->sort as $key => $order) {
1535
				$column = $this->getColumn($key);
1536
1537
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1538
					$this->sort_callback = $column->getSortableCallback();
1539
				}
1540
			}
1541
		}
1542
	}
1543
1544
1545
	/********************************************************************************
1546
	 *                                    EXPORTS                                   *
1547
	 ********************************************************************************/
1548
1549
1550
	/**
1551
	 * Add export of type callback
1552
	 * @param string $text
1553
	 * @param callable $callback
1554
	 * @param boolean $filtered
1555
	 * @return Export\Export
1556
	 */
1557
	public function addExportCallback($text, $callback, $filtered = FALSE)
1558
	{
1559
		if (!is_callable($callback)) {
1560
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1561
		}
1562
1563
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
1564
	}
1565
1566
1567
	/**
1568
	 * Add already implemented csv export
1569
	 * @param string      $text
1570
	 * @param string      $csv_file_name
1571
	 * @param string|null $output_encoding
1572
	 * @param string|null $delimiter
1573
	 * @return Export\Export
1574
	 */
1575
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1576
	{
1577
		return $this->addToExports(new Export\ExportCsv(
1578
			$text,
1579
			$csv_file_name,
1580
			FALSE,
1581
			$output_encoding,
1582
			$delimiter
1583
		));
1584
	}
1585
1586
1587
	/**
1588
	 * Add already implemented csv export, but for filtered data
1589
	 * @param string      $text
1590
	 * @param string      $csv_file_name
1591
	 * @param string|null $output_encoding
1592
	 * @param string|null $delimiter
1593
	 * @return Export\Export
1594
	 */
1595
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1596
	{
1597
		return $this->addToExports(new Export\ExportCsv(
1598
			$text,
1599
			$csv_file_name,
1600
			TRUE,
1601
			$output_encoding,
1602
			$delimiter
1603
		));
1604
	}
1605
1606
1607
	/**
1608
	 * Add export to array
1609
	 * @param Export\Export $export
1610
	 * @return Export\Export
1611
	 */
1612
	protected function addToExports(Export\Export $export)
1613
	{
1614
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1615
1616
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1617
1618
		return $this->exports[$id] = $export;
1619
	}
1620
1621
1622
	public function resetExportsLinks()
1623
	{
1624
		foreach ($this->exports as $id => $export) {
1625
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1626
		}
1627
	}
1628
1629
1630
	/********************************************************************************
1631
	 *                                 GROUP ACTIONS                                *
1632
	 ********************************************************************************/
1633
1634
1635
	/**
1636
	 * Alias for add group select action
1637
	 * @param string $title
1638
	 * @param array  $options
1639
	 * @return GroupAction\GroupAction
1640
	 */
1641
	public function addGroupAction($title, $options = [])
1642
	{
1643
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1644
	}
1645
1646
	/**
1647
	 * Add group action (select box)
1648
	 * @param string $title
1649
	 * @param array  $options
1650
	 * @return GroupAction\GroupAction
1651
	 */
1652
	public function addGroupSelectAction($title, $options = [])
1653
	{
1654
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1655
	}
1656
1657
	/**
1658
	 * Add group action (text input)
1659
	 * @param string $title
1660
	 * @return GroupAction\GroupAction
1661
	 */
1662
	public function addGroupTextAction($title)
1663
	{
1664
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1665
	}
1666
1667
	/**
1668
	 * Get collection of all group actions
1669
	 * @return GroupAction\GroupActionCollection
1670
	 */
1671
	public function getGroupActionCollection()
1672
	{
1673
		if (!$this->group_action_collection) {
1674
			$this->group_action_collection = new GroupAction\GroupActionCollection();
1675
		}
1676
1677
		return $this->group_action_collection;
1678
	}
1679
1680
1681
	/**
1682
	 * Has datagrid some group actions?
1683
	 * @return boolean
1684
	 */
1685
	public function hasGroupActions()
1686
	{
1687
		return (bool) $this->group_action_collection;
1688
	}
1689
1690
1691
	/********************************************************************************
1692
	 *                                   HANDLERS                                   *
1693
	 ********************************************************************************/
1694
1695
1696
	/**
1697
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1698
	 * @param  int  $page
1699
	 * @return void
1700
	 */
1701
	public function handlePage($page)
1702
	{
1703
		/**
1704
		 * Session stuff
1705
		 */
1706
		$this->page = $page;
1707
		$this->saveSessionData('_grid_page', $page);
1708
1709
		$this->reload(['table']);
1710
	}
1711
1712
1713
	/**
1714
	 * Handler for sorting
1715
	 * @param array $sort
1716
	 * @return void
1717
	 */
1718
	public function handleSort(array $sort)
1719
	{
1720
		$new_sort = [];
1721
1722
		/**
1723
		 * Find apropirate column
1724
		 */
1725
		foreach ($sort as $key => $value) {
1726
			if (empty($this->columns[$key])) {
1727
				throw new DataGridException("Column <$key> not found");
1728
			}
1729
1730
			$column = $this->columns[$key];
1731
			$new_sort = [$column->getSortingColumn() => $value];
1732
1733
			/**
1734
			 * Pagination may be reseted after sorting
1735
			 */
1736
			if ($column->sortableResetPagination()) {
1737
				$this->page = 1;
1738
				$this->saveSessionData('_grid_page', 1);
1739
			}
1740
1741
			/**
1742
			 * Custom sorting callback may be applied
1743
			 */
1744
			if ($column->getSortableCallback()) {
1745
				$this->sort_callback = $column->getSortableCallback();
1746
			}
1747
		}
1748
1749
		/**
1750
		 * Session stuff
1751
		 */
1752
		$this->sort = $new_sort;
1753
		$this->saveSessionData('_grid_sort', $this->sort);
1754
1755
		$this->reload(['table']);
1756
	}
1757
1758
1759
	/**
1760
	 * handler for reseting the filter
1761
	 * @return void
1762
	 */
1763
	public function handleResetFilter()
1764
	{
1765
		/**
1766
		 * Session stuff
1767
		 */
1768
		$this->deleteSesssionData('_grid_page');
1769
1770
		if ($this->default_filter_use_on_reset) {
1771
			$this->deleteSesssionData('_grid_has_filtered');
1772
		}
1773
1774
		foreach ($this->getSessionData() as $key => $value) {
1775
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page', '_grid_has_filtered'])) {
1776
				$this->deleteSesssionData($key);
1777
			}
1778
		}
1779
1780
		$this->filter = [];
1781
1782
		$this->reload(['grid']);
1783
	}
1784
1785
1786
	/**
1787
	 * Handler for export
1788
	 * @param  int $id Key for particular export class in array $this->exports
1789
	 * @return void
1790
	 */
1791
	public function handleExport($id)
1792
	{
1793
		if (!isset($this->exports[$id])) {
1794
			throw new Nette\Application\ForbiddenRequestException;
1795
		}
1796
1797
		if (!empty($this->columns_export_order)) {
1798
			$this->setColumnsOrder($this->columns_export_order);
1799
		}
1800
1801
		$export = $this->exports[$id];
1802
1803
		if ($export->isFiltered()) {
1804
			$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...
1805
			$filter    = $this->assableFilters();
1806
		} else {
1807
			$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...
1808
			$filter    = [];
1809
		}
1810
1811
		if (NULL === $this->dataModel) {
1812
			throw new DataGridException('You have to set a data source first.');
1813
		}
1814
1815
		$rows = [];
1816
1817
		$items = Nette\Utils\Callback::invokeArgs(
1818
			[$this->dataModel, 'filterData'], [
1819
				NULL,
1820
				new Sorting($this->sort, $this->sort_callback),
1821
				$filter
1822
			]
1823
		);
1824
1825
		foreach ($items as $item) {
1826
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1827
		}
1828
1829
		if ($export instanceof Export\ExportCsv) {
1830
			$export->invoke($rows, $this);
1831
		} else {
1832
			$export->invoke($items, $this);
1833
		}
1834
1835
		if ($export->isAjax()) {
1836
			$this->reload();
1837
		}
1838
	}
1839
1840
1841
	/**
1842
	 * Handler for getting children of parent item (e.g. category)
1843
	 * @param  int $parent
1844
	 * @return void
1845
	 */
1846
	public function handleGetChildren($parent)
1847
	{
1848
		$this->setDataSource(
1849
			call_user_func($this->tree_view_children_callback, $parent)
1850
		);
1851
1852
		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...
1853
			$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...
1854
			$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...
1855
1856
			$this->redrawControl('items');
1857
1858
			$this->onRedraw();
1859
		} else {
1860
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1861
		}
1862
	}
1863
1864
1865
	/**
1866
	 * Handler for getting item detail
1867
	 * @param  mixed $id
1868
	 * @return void
1869
	 */
1870
	public function handleGetItemDetail($id)
1871
	{
1872
		$this->getTemplate()->add('toggle_detail', $id);
1873
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1874
1875
		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...
1876
			$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...
1877
			$this->redrawControl('items');
1878
1879
			$this->onRedraw();
1880
		} else {
1881
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1882
		}
1883
	}
1884
1885
1886
	/**
1887
	 * Handler for inline editing
1888
	 * @param  mixed $id
1889
	 * @param  mixed $key
1890
	 * @return void
1891
	 */
1892
	public function handleEdit($id, $key)
1893
	{
1894
		$column = $this->getColumn($key);
1895
		$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...
1896
1897
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1898
	}
1899
1900
1901
	/**
1902
	 * Redraw $this
1903
	 * @return void
1904
	 */
1905
	public function reload($snippets = [])
1906
	{
1907
		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...
1908
			$this->redrawControl('tbody');
1909
			$this->redrawControl('pagination');
1910
1911
			/**
1912
			 * manualy reset exports links...
1913
			 */
1914
			$this->resetExportsLinks();
1915
			$this->redrawControl('exports');
1916
1917
			foreach ($snippets as $snippet) {
1918
				$this->redrawControl($snippet);
1919
			}
1920
1921
			$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...
1922
1923
			$this->onRedraw();
1924
		} else {
1925
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1926
		}
1927
	}
1928
1929
1930
	/**
1931
	 * Handler for column status
1932
	 * @param  string $id
1933
	 * @param  string $key
1934
	 * @param  string $value
1935
	 * @return void
1936
	 */
1937
	public function handleChangeStatus($id, $key, $value)
1938
	{
1939
		if (empty($this->columns[$key])) {
1940
			throw new DataGridException("ColumnStatus[$key] does not exist");
1941
		}
1942
1943
		$this->columns[$key]->onChange($id, $value);
1944
	}
1945
1946
1947
	/**
1948
	 * Redraw just one row via ajax
1949
	 * @param  int   $id
1950
	 * @param  mixed $primary_where_column
1951
	 * @return void
1952
	 */
1953
	public function redrawItem($id, $primary_where_column = NULL)
1954
	{
1955
		$this->snippets_set = TRUE;
1956
1957
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1958
1959
		$this->redrawControl('items');
1960
		$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...
1961
1962
		$this->onRedraw();
1963
	}
1964
1965
1966
	/**
1967
	 * Tell datagrid to display all columns
1968
	 * @return void
1969
	 */
1970
	public function handleShowAllColumns()
1971
	{
1972
		$this->deleteSesssionData('_grid_hidden_columns');
1973
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1974
1975
		$this->redrawControl();
1976
1977
		$this->onRedraw();
1978
	}
1979
1980
1981
	/**
1982
	 * Tell datagrid to display default columns
1983
	 * @return void
1984
	 */
1985
	public function handleShowDefaultColumns()
1986
	{
1987
		$this->deleteSesssionData('_grid_hidden_columns');
1988
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
1989
1990
		$this->redrawControl();
1991
1992
		$this->onRedraw();
1993
	}
1994
1995
1996
	/**
1997
	 * Reveal particular column
1998
	 * @param  string $column
1999
	 * @return void
2000
	 */
2001 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...
2002
	{
2003
		$columns = $this->getSessionData('_grid_hidden_columns');
2004
2005
		if (!empty($columns)) {
2006
			$pos = array_search($column, $columns);
2007
2008
			if ($pos !== FALSE) {
2009
				unset($columns[$pos]);
2010
			}
2011
		}
2012
2013
		$this->saveSessionData('_grid_hidden_columns', $columns);
2014
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2015
2016
		$this->redrawControl();
2017
2018
		$this->onRedraw();
2019
	}
2020
2021
2022
	/**
2023
	 * Notice datagrid to not display particular columns
2024
	 * @param  string $column
2025
	 * @return void
2026
	 */
2027 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...
2028
	{
2029
		/**
2030
		 * Store info about hiding a column to session
2031
		 */
2032
		$columns = $this->getSessionData('_grid_hidden_columns');
2033
2034
		if (empty($columns)) {
2035
			$columns = [$column];
2036
		} else if (!in_array($column, $columns)) {
2037
			array_push($columns, $column);
2038
		}
2039
2040
		$this->saveSessionData('_grid_hidden_columns', $columns);
2041
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2042
2043
		$this->redrawControl();
2044
2045
		$this->onRedraw();
2046
	}
2047
2048
2049
	public function handleActionCallback($__key, $__id)
2050
	{
2051
		$action = $this->getAction($__key);
2052
2053
		if (!($action instanceof Column\ActionCallback)) {
2054
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2055
		}
2056
2057
		$action->onClick($__id);
2058
	}
2059
2060
2061
	/********************************************************************************
2062
	 *                                  PAGINATION                                  *
2063
	 ********************************************************************************/
2064
2065
2066
	/**
2067
	 * Set options of select "items_per_page"
2068
	 * @param array $items_per_page_list
2069
	 * @return static
2070
	 */
2071
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
2072
	{
2073
		$this->items_per_page_list = $items_per_page_list;
2074
2075
		if ($include_all) {
2076
			$this->items_per_page_list[] = 'all';
2077
		}
2078
2079
		return $this;
2080
	}
2081
2082
2083
	/**
2084
	 * Paginator factory
2085
	 * @return Components\DataGridPaginator\DataGridPaginator
2086
	 */
2087
	public function createComponentPaginator()
2088
	{
2089
		/**
2090
		 * Init paginator
2091
		 */
2092
		$component = new Components\DataGridPaginator\DataGridPaginator(
2093
			$this->getTranslator()
2094
		);
2095
		$paginator = $component->getPaginator();
2096
2097
		$paginator->setPage($this->page);
2098
		$paginator->setItemsPerPage($this->getPerPage());
2099
2100
		return $component;
2101
	}
2102
2103
2104
	/**
2105
	 * PerPage form factory
2106
	 * @return Form
2107
	 */
2108
	public function createComponentPerPage()
2109
	{
2110
		$form = new Form;
2111
2112
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
2113
			->setValue($this->getPerPage());
2114
2115
		$form->addSubmit('submit', '');
2116
2117
		$saveSessionData = [$this, 'saveSessionData'];
2118
2119
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
2120
			/**
2121
			 * Session stuff
2122
			 */
2123
			$saveSessionData('_grid_per_page', $values->per_page);
2124
2125
			/**
2126
			 * Other stuff
2127
			 */
2128
			$this->per_page = $values->per_page;
2129
			$this->reload();
2130
		};
2131
2132
		return $form;
2133
	}
2134
2135
2136
	/**
2137
	 * Get parameter per_page
2138
	 * @return int
2139
	 */
2140
	public function getPerPage()
2141
	{
2142
		$items_per_page_list = $this->getItemsPerPageList();
2143
2144
		$per_page = $this->per_page ?: reset($items_per_page_list);
2145
2146
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2147
			$per_page = reset($items_per_page_list);
2148
		}
2149
2150
		return $per_page;
2151
	}
2152
2153
2154
	/**
2155
	 * Get associative array of items_per_page_list
2156
	 * @return array
2157
	 */
2158
	public function getItemsPerPageList()
2159
	{
2160
		if (empty($this->items_per_page_list)) {
2161
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2162
		}
2163
2164
		$list = array_flip($this->items_per_page_list);
2165
2166
		foreach ($list as $key => $value) {
2167
			$list[$key] = $key;
2168
		}
2169
2170
		if (array_key_exists('all', $list)) {
2171
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2172
		}
2173
2174
		return $list;
2175
	}
2176
2177
2178
	/**
2179
	 * Order Grid to "be paginated"
2180
	 * @param bool $do
2181
	 * @return static
2182
	 */
2183
	public function setPagination($do)
2184
	{
2185
		$this->do_paginate = (bool) $do;
2186
2187
		return $this;
2188
	}
2189
2190
2191
	/**
2192
	 * Tell whether Grid is paginated
2193
	 * @return bool
2194
	 */
2195
	public function isPaginated()
2196
	{
2197
		return $this->do_paginate;
2198
	}
2199
2200
2201
	/**
2202
	 * Return current paginator class
2203
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2204
	 */
2205
	public function getPaginator()
2206
	{
2207
		if ($this->isPaginated() && $this->per_page !== 'all') {
2208
			return $this['paginator'];
2209
		}
2210
2211
		return NULL;
2212
	}
2213
2214
2215
	/********************************************************************************
2216
	 *                                     I18N                                     *
2217
	 ********************************************************************************/
2218
2219
2220
	/**
2221
	 * Set datagrid translator
2222
	 * @param Nette\Localization\ITranslator $translator
2223
	 * @return static
2224
	 */
2225
	public function setTranslator(Nette\Localization\ITranslator $translator)
2226
	{
2227
		$this->translator = $translator;
2228
2229
		return $this;
2230
	}
2231
2232
2233
	/**
2234
	 * Get translator for datagrid
2235
	 * @return Nette\Localization\ITranslator
2236
	 */
2237
	public function getTranslator()
2238
	{
2239
		if (!$this->translator) {
2240
			$this->translator = new Localization\SimpleTranslator;
2241
		}
2242
2243
		return $this->translator;
2244
	}
2245
2246
2247
	/********************************************************************************
2248
	 *                                 COLUMNS ORDER                                *
2249
	 ********************************************************************************/
2250
2251
2252
	/**
2253
	 * Set order of datagrid columns
2254
	 * @param array $order
2255
	 * @return static
2256
	 */
2257
	public function setColumnsOrder($order)
2258
	{
2259
		$new_order = [];
2260
2261
		foreach ($order as $key) {
2262
			if (isset($this->columns[$key])) {
2263
				$new_order[$key] = $this->columns[$key];
2264
			}
2265
		}
2266
2267
		if (sizeof($new_order) === sizeof($this->columns)) {
2268
			$this->columns = $new_order;
2269
		} else {
2270
			throw new DataGridException('When changing columns order, you have to specify all columns');
2271
		}
2272
2273
		return $this;
2274
	}
2275
2276
2277
	/**
2278
	 * Columns order may be different for export and normal grid
2279
	 * @param array $order
2280
	 */
2281
	public function setColumnsExportOrder($order)
2282
	{
2283
		$this->columns_export_order = (array) $order;
2284
	}
2285
2286
2287
	/********************************************************************************
2288
	 *                                SESSION & URL                                 *
2289
	 ********************************************************************************/
2290
2291
2292
	/**
2293
	 * Find some unique session key name
2294
	 * @return string
2295
	 */
2296
	public function getSessionSectionName()
2297
	{
2298
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2299
	}
2300
2301
2302
	/**
2303
	 * Should datagrid remember its filters/pagination/etc using session?
2304
	 * @param bool $remember
2305
	 * @return static
2306
	 */
2307
	public function setRememberState($remember = TRUE)
2308
	{
2309
		$this->remember_state = (bool) $remember;
2310
2311
		return $this;
2312
	}
2313
2314
2315
	/**
2316
	 * Should datagrid refresh url using history API?
2317
	 * @param bool $refresh
2318
	 * @return static
2319
	 */
2320
	public function setRefreshUrl($refresh = TRUE)
2321
	{
2322
		$this->refresh_url = (bool) $refresh;
2323
2324
2325
		return $this;
2326
	}
2327
2328
2329
	/**
2330
	 * Get session data if functionality is enabled
2331
	 * @param  string $key
2332
	 * @return mixed
2333
	 */
2334
	public function getSessionData($key = NULL, $default_value = NULL)
2335
	{
2336
		if (!$this->remember_state) {
2337
			return $key ? $default_value : [];
2338
		}
2339
2340
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2341
	}
2342
2343
2344
	/**
2345
	 * Save session data - just if it is enabled
2346
	 * @param  string $key
2347
	 * @param  mixed  $value
2348
	 * @return void
2349
	 */
2350
	public function saveSessionData($key, $value)
2351
	{
2352
		if ($this->remember_state) {
2353
			$this->grid_session->{$key} = $value;
2354
		}
2355
	}
2356
2357
2358
	/**
2359
	 * Delete session data
2360
	 * @return void
2361
	 */
2362
	public function deleteSesssionData($key)
2363
	{
2364
		unset($this->grid_session->{$key});
2365
	}
2366
2367
2368
	/********************************************************************************
2369
	 *                                  ITEM DETAIL                                 *
2370
	 ********************************************************************************/
2371
2372
2373
	/**
2374
	 * Get items detail parameters
2375
	 * @return array
2376
	 */
2377
	public function getItemsDetail()
2378
	{
2379
		return $this->items_detail;
2380
	}
2381
2382
2383
	/**
2384
	 * Items can have thair detail - toggled
2385
	 * @param mixed $detail callable|string|bool
2386
	 * @param bool|NULL $primary_where_column
2387
	 * @return Column\ItemDetail
2388
	 */
2389
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2390
	{
2391
		if ($this->isSortable()) {
2392
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2393
		}
2394
2395
		$this->items_detail = new Column\ItemDetail(
2396
			$this,
2397
			$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...
2398
		);
2399
2400
		if (is_string($detail)) {
2401
			/**
2402
			 * Item detail will be in separate template
2403
			 */
2404
			$this->items_detail->setType('template');
2405
			$this->items_detail->setTemplate($detail);
2406
2407
		} else if (is_callable($detail)) {
2408
			/**
2409
			 * Item detail will be rendered via custom callback renderer
2410
			 */
2411
			$this->items_detail->setType('renderer');
2412
			$this->items_detail->setRenderer($detail);
2413
2414
		} else if (TRUE === $detail) {
2415
			/**
2416
			 * Item detail will be rendered probably via block #detail
2417
			 */
2418
			$this->items_detail->setType('block');
2419
2420
		} else {
2421
			throw new DataGridException(
2422
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2423
			);
2424
		}
2425
2426
		return $this->items_detail;
2427
	}
2428
2429
2430
	/**
2431
	 * @param callable $callable_set_container 
2432
	 * @return static
2433
	 */
2434
	public function setItemsDetailForm(callable $callable_set_container)
2435
	{
2436
		if ($this->items_detail instanceof Column\ItemDetail) {
2437
			$this->items_detail->setForm(
2438
				new Utils\ItemDetailForm($callable_set_container)
2439
			);
2440
2441
			return $this;
2442
		}
2443
2444
		throw new DataGridException('Please set the ItemDetail first.');
2445
	}
2446
2447
2448
	/**
2449
	 * @return Nette\Forms\Container|NULL
2450
	 */
2451
	public function getItemDetailForm()
2452
	{
2453
		if ($this->items_detail instanceof Column\ItemDetail) {
2454
			return $this->items_detail->getForm();
2455
		}
2456
2457
		return NULL;
2458
	}
2459
2460
2461
	/********************************************************************************
2462
	 *                                ROW PRIVILEGES                                *
2463
	 ********************************************************************************/
2464
2465
2466
	/**
2467
	 * @param  callable $condition
2468
	 * @return void
2469
	 */
2470
	public function allowRowsGroupAction(callable $condition)
2471
	{
2472
		$this->row_conditions['group_action'] = $condition;
2473
	}
2474
2475
2476
	/**
2477
	 * @param  string   $key
2478
	 * @param  callable $condition
2479
	 * @return void
2480
	 */
2481
	public function allowRowsAction($key, callable $condition)
2482
	{
2483
		$this->row_conditions['action'][$key] = $condition;
2484
	}
2485
2486
2487
	/**
2488
	 * @param  string      $name
2489
	 * @param  string|null $key
2490
	 * @return bool|callable
2491
	 */
2492
	public function getRowCondition($name, $key = NULL)
2493
	{
2494
		if (!isset($this->row_conditions[$name])) {
2495
			return FALSE;
2496
		}
2497
2498
		$condition = $this->row_conditions[$name];
2499
2500
		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...
2501
			return $condition;
2502
		}
2503
2504
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2505
	}
2506
2507
2508
	/********************************************************************************
2509
	 *                               COLUMN CALLBACK                                *
2510
	 ********************************************************************************/
2511
2512
2513
	/**
2514
	 * @param  string   $key
2515
	 * @param  callable $callback
2516
	 * @return void
2517
	 */
2518
	public function addColumnCallback($key, callable $callback)
2519
	{
2520
		$this->column_callbacks[$key] = $callback;
2521
	}
2522
2523
2524
	/**
2525
	 * @param  string $key
2526
	 * @return callable|null
2527
	 */
2528
	public function getColumnCallback($key)
2529
	{
2530
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2531
	}
2532
2533
2534
	/********************************************************************************
2535
	 *                                 INLINE EDIT                                  *
2536
	 ********************************************************************************/
2537
2538
2539
	/**
2540
	 * @return InlineEdit
2541
	 */
2542
	public function addInlineEdit($primary_where_column = NULL)
2543
	{
2544
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2545
2546
		return $this->inlineEdit;
2547
	}
2548
2549
2550
	/**
2551
	 * @return InlineEdit|null
2552
	 */
2553
	public function getInlineEdit()
2554
	{
2555
		return $this->inlineEdit;
2556
	}
2557
2558
2559
	/**
2560
	 * @param  mixed $id
2561
	 * @return void
2562
	 */
2563
	public function handleInlineEdit($id)
2564
	{
2565
		if ($this->inlineEdit) {
2566
			$this->inlineEdit->setItemId($id);
2567
2568
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2569
2570
			$this['filter']['inline_edit']->addHidden('_id', $id);
2571
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2572
2573
			$this->redrawItem($id, $primary_where_column);
2574
		}
2575
	}
2576
2577
2578
	/********************************************************************************
2579
	 *                                  INLINE ADD                                  *
2580
	 ********************************************************************************/
2581
2582
2583
	/**
2584
	 * @return InlineEdit
2585
	 */
2586
	public function addInlineAdd()
2587
	{
2588
		$this->inlineAdd = new InlineEdit($this);
2589
2590
		$this->inlineAdd
2591
			->setIcon('plus')
2592
			->setClass('btn btn-xs btn-default');
2593
2594
		return $this->inlineAdd;
2595
	}
2596
2597
2598
	/**
2599
	 * @return InlineEdit|null
2600
	 */
2601
	public function getInlineAdd()
2602
	{
2603
		return $this->inlineAdd;
2604
	}
2605
2606
2607
	/********************************************************************************
2608
	 *                               HIDEABLE COLUMNS                               *
2609
	 ********************************************************************************/
2610
2611
2612
	/**
2613
	 * Can datagrid hide colums?
2614
	 * @return boolean
2615
	 */
2616
	public function canHideColumns()
2617
	{
2618
		return (bool) $this->can_hide_columns;
2619
	}
2620
2621
2622
	/**
2623
	 * Order Grid to set columns hideable.
2624
	 * @return static
2625
	 */
2626
	public function setColumnsHideable()
2627
	{
2628
		$this->can_hide_columns = TRUE;
2629
2630
		return $this;
2631
	}
2632
2633
2634
	/********************************************************************************
2635
	 *                                   INTERNAL                                   *
2636
	 ********************************************************************************/
2637
2638
2639
	/**
2640
	 * Get cont of columns
2641
	 * @return int
2642
	 */
2643
	public function getColumnsCount()
2644
	{
2645
		$count = sizeof($this->getColumns());
2646
2647
		if (!empty($this->actions)
2648
			|| $this->isSortable()
2649
			|| $this->getItemsDetail()
2650
			|| $this->getInlineEdit()
2651
			|| $this->getInlineAdd()) {
2652
			$count++;
2653
		}
2654
2655
		if ($this->hasGroupActions()) {
2656
			$count++;
2657
		}
2658
2659
		return $count;
2660
	}
2661
2662
2663
	/**
2664
	 * Get primary key of datagrid data source
2665
	 * @return string
2666
	 */
2667
	public function getPrimaryKey()
2668
	{
2669
		return $this->primary_key;
2670
	}
2671
2672
2673
	/**
2674
	 * Get set of set columns
2675
	 * @return Column\IColumn[]
2676
	 */
2677
	public function getColumns()
2678
	{
2679
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2680
			$columns_to_hide = [];
2681
2682
			foreach ($this->columns as $key => $column) {
2683
				if ($column->getDefaultHide()) {
2684
					$columns_to_hide[] = $key;
2685
				}
2686
			}
2687
2688
			if (!empty($columns_to_hide)) {
2689
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2690
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2691
			}
2692
		}
2693
2694
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2695
		
2696
		foreach ($hidden_columns as $column) {
2697
			if (!empty($this->columns[$column])) {
2698
				$this->columns_visibility[$column] = [
2699
					'visible' => FALSE,
2700
					'name' => $this->columns[$column]->getName()
2701
				];
2702
2703
				$this->removeColumn($column);
2704
			}
2705
		}
2706
2707
		return $this->columns;
2708
	}
2709
2710
2711
	/**
2712
	 * @return PresenterComponent
2713
	 */
2714
	public function getParent()
2715
	{
2716
		$parent = parent::getParent();
2717
2718
		if (!($parent instanceof PresenterComponent)) {
2719
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2720
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2721
			);
2722
		}
2723
2724
		return $parent;
2725
	}
2726
2727
2728
	/**
2729
	 * Some of datagrid columns is hidden by default
2730
	 * @param bool $default_hide
2731
	 */
2732
	public function setSomeColumnDefaultHide($default_hide)
2733
	{
2734
		$this->some_column_default_hide = $default_hide;
2735
	}
2736
2737
2738
	/**
2739
	 * Are some of columns hidden bydefault?
2740
	 */
2741
	public function hasSomeColumnDefaultHide()
2742
	{
2743
		return $this->some_column_default_hide;
2744
	}
2745
2746
}
2747