Completed
Pull Request — master (#227)
by Martin
04:49 queued 01:55
created

DataGrid::setColumnsSummary()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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