Completed
Pull Request — master (#227)
by Martin
02:53
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
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid;
10
11
use Nette;
12
use Nette\Application\UI\Link;
13
use Nette\Application\UI\PresenterComponent;
14
use Ublaboo\DataGrid\Utils\ArraysHelper;
15
use Nette\Application\UI\Form;
16
use Ublaboo\DataGrid\Exception\DataGridException;
17
use Ublaboo\DataGrid\Exception\DataGridHasToBeAttachedToPresenterComponentException;
18
use Ublaboo\DataGrid\Utils\Sorting;
19
use Ublaboo\DataGrid\InlineEdit\InlineEdit;
20
21
/**
22
 * @method onRedraw()
23
 * @method onRender()
24
 * @method onColumnAdd()
25
 */
26
class DataGrid extends Nette\Application\UI\Control
27
{
28
29
	/**
30
	 * @var callable[]
31
	 */
32
	public $onRedraw;
33
34
	/**
35
	 * @var callable[]
36
	 */
37
	public $onRender = [];
38
39
	/**
40
	 * @var callable[]
41
	 */
42
	public $onColumnAdd;
43
44
	/**
45
	 * @var string
46
	 */
47
	public static $icon_prefix = 'fa fa-';
48
49
	/**
50
	 * When set to TRUE, datagrid throws an exception
51
	 * 	when tring to get related entity within join and entity does not exist
52
	 * @var bool
53
	 */
54
	public $strict_entity_property = FALSE;
55
56
	/**
57
	 * @var int
58
	 * @persistent
59
	 */
60
	public $page = 1;
61
62
	/**
63
	 * @var int|string
64
	 * @persistent
65
	 */
66
	public $per_page;
67
68
	/**
69
	 * @var array
70
	 * @persistent
71
	 */
72
	public $sort = [];
73
74
	/**
75
	 * @var array
76
	 */
77
	public $default_sort = [];
78
79
	/**
80
	 * @var array
81
	 */
82
	public $default_filter = [];
83
84
	/**
85
	 * @var bool
86
	 */
87
	public $default_filter_use_on_reset = TRUE;
88
89
	/**
90
	 * @var array
91
	 * @persistent
92
	 */
93
	public $filter = [];
94
95
	/**
96
	 * @var callable|null
97
	 */
98
	protected $sort_callback = NULL;
99
100
	/**
101
	 * @var bool
102
	 */
103
	protected $use_happy_components = TRUE;
104
105
	/**
106
	 * @var callable
107
	 */
108
	protected $rowCallback;
109
110
	/**
111
	 * @var array
112
	 */
113
	protected $items_per_page_list;
114
115
	/**
116
	 * @var string
117
	 */
118
	protected $template_file;
119
120
	/**
121
	 * @var Column\IColumn[]
122
	 */
123
	protected $columns = [];
124
125
	/**
126
	 * @var Column\Action[]
127
	 */
128
	protected $actions = [];
129
130
	/**
131
	 * @var GroupAction\GroupActionCollection
132
	 */
133
	protected $group_action_collection;
134
135
	/**
136
	 * @var Filter\Filter[]
137
	 */
138
	protected $filters = [];
139
140
	/**
141
	 * @var Export\Export[]
142
	 */
143
	protected $exports = [];
144
145
	/**
146
	 * @var DataModel
147
	 */
148
	protected $dataModel;
149
150
	/**
151
	 * @var DataFilter
152
	 */
153
	protected $dataFilter;
154
155
	/**
156
	 * @var string
157
	 */
158
	protected $primary_key = 'id';
159
160
	/**
161
	 * @var bool
162
	 */
163
	protected $do_paginate = TRUE;
164
165
	/**
166
	 * @var bool
167
	 */
168
	protected $csv_export = TRUE;
169
170
	/**
171
	 * @var bool
172
	 */
173
	protected $csv_export_filtered = TRUE;
174
175
	/**
176
	 * @var bool
177
	 */
178
	protected $sortable = FALSE;
179
180
	/**
181
	 * @var string
182
	 */
183
	protected $sortable_handler = 'sort!';
184
185
	/**
186
	 * @var string
187
	 */
188
	protected $original_template;
189
190
	/**
191
	 * @var array
192
	 */
193
	protected $redraw_item;
194
195
	/**
196
	 * @var mixed
197
	 */
198
	protected $translator;
199
200
	/**
201
	 * @var bool
202
	 */
203
	protected $force_filter_active;
204
205
	/**
206
	 * @var callable
207
	 */
208
	protected $tree_view_children_callback;
209
210
	/**
211
	 * @var callable
212
	 */
213
	protected $tree_view_has_children_callback;
214
215
	/**
216
	 * @var string
217
	 */
218
	protected $tree_view_has_children_column;
219
220
	/**
221
	 * @var bool
222
	 */
223
	protected $outer_filter_rendering = FALSE;
224
225
	/**
226
	 * @var array
227
	 */
228
	protected $columns_export_order = [];
229
230
	/**
231
	 * @var bool
232
	 */
233
	protected $remember_state = TRUE;
234
235
	/**
236
	 * @var bool
237
	 */
238
	protected $refresh_url = TRUE;
239
240
	/**
241
	 * @var Nette\Http\SessionSection
242
	 */
243
	protected $grid_session;
244
245
	/**
246
	 * @var Column\ItemDetail
247
	 */
248
	protected $items_detail;
249
250
	/**
251
	 * @var array
252
	 */
253
	protected $row_conditions = [
254
		'group_action' => FALSE,
255
		'action' => []
256
	];
257
258
	/**
259
	 * @var array
260
	 */
261
	protected $column_callbacks = [];
262
263
	/**
264
	 * @var bool
265
	 */
266
	protected $can_hide_columns = FALSE;
267
268
	/**
269
	 * @var array
270
	 */
271
	protected $columns_visibility = [];
272
273
	/**
274
	 * @var array
275
	 */
276
	protected $columns_summary = [];
277
278
	/**
279
	 * @var InlineEdit
280
	 */
281
	protected $inlineEdit;
282
283
	/**
284
	 * @var InlineEdit
285
	 */
286
	protected $inlineAdd;
287
288
	/**
289
	 * @var bool
290
	 */
291
	protected $snippets_set = FALSE;
292
293
	/**
294
	 * @var bool
295
	 */
296
	protected $some_column_default_hide = FALSE;
297
298
299
	/**
300
	 * @param Nette\ComponentModel\IContainer|NULL $parent
301
	 * @param string                               $name
302
	 */
303
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
304
	{
305
		parent::__construct($parent, $name);
306
307
		$this->monitor('Nette\Application\UI\Presenter');
308
309
		/**
310
		 * Try to find previous filters, pagination, per_page and other values in session
311
		 */
312
		$this->onRender[] = [$this, 'findSessionValues'];
313
314
		/**
315
		 * Find default filter values
316
		 */
317
		$this->onRender[] = [$this, 'findDefaultFilter'];
318
319
		/**
320
		 * Find default sort
321
		 */
322
		$this->onRender[] = [$this, 'findDefaultSort'];
323
	}
324
325
326
	/**
327
	 * {inheritDoc}
328
	 * @return void
329
	 */
330
	public function attached($presenter)
331
	{
332
		parent::attached($presenter);
333
334
		if ($presenter instanceof Nette\Application\UI\Presenter) {
335
			/**
336
			 * Get session
337
			 */
338
			if ($this->remember_state) {
339
				$this->grid_session = $presenter->getSession($this->getSessionSectionName());
0 ignored issues
show
Documentation Bug introduced by
It seems like $presenter->getSession($...etSessionSectionName()) can also be of type object<Nette\Http\Session>. However, the property $grid_session is declared as type object<Nette\Http\SessionSection>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
340
			}
341
		}
342
	}
343
344
345
	/********************************************************************************
346
	 *                                  RENDERING                                   *
347
	 ********************************************************************************/
348
349
350
	/**
351
	 * Render template
352
	 * @return void
353
	 */
354
	public function render()
355
	{
356
		/**
357
		 * Check whether datagrid has set some columns, initiated data source, etc
358
		 */
359
		if (!($this->dataModel instanceof DataModel)) {
360
			throw new DataGridException('You have to set a data source first.');
361
		}
362
363
		if (empty($this->columns)) {
364
			throw new DataGridException('You have to add at least one column.');
365
		}
366
367
		$this->getTemplate()->setTranslator($this->getTranslator());
368
369
		/**
370
		 * Invoke possible events
371
		 */
372
		$this->onRender($this);
0 ignored issues
show
Unused Code introduced by
The call to DataGrid::onRender() has too many arguments starting with $this.

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

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

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

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