Completed
Push — master ( 930403...14fea6 )
by Pavel
03:18
created

DataGrid::createComponentPerPage()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 26
Code Lines 11

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 26
rs 8.8571
cc 1
eloc 11
nc 1
nop 0

1 Method

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