Completed
Push — master ( ca3a89...f348a3 )
by Pavel
07:11 queued 04:12
created

DataGrid::getColumnsSummary()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

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

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

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

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

Loading history...
374
375
		/**
376
		 * Prepare data for rendering (datagrid may render just one item)
377
		 */
378
		$rows = [];
379
380
		if (!empty($this->redraw_item)) {
381
			$items = $this->dataModel->filterRow($this->redraw_item);
382
		} else {
383
			$items = Nette\Utils\Callback::invokeArgs(
384
				[$this->dataModel, 'filterData'],
385
				[
386
					$this->getPaginator(),
387
					new Sorting($this->sort, $this->sort_callback),
388
					$this->assableFilters()
389
				]
390
			);
391
		}
392
393
		$callback = $this->rowCallback ?: NULL;
394
395
		foreach ($items as $item) {
396
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
397
398
			if ($callback) {
399
				$callback($item, $row->getControl());
400
			}
401
		}
402
403
		if ($this->isTreeView()) {
404
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
405
		}
406
407
		$this->getTemplate()->add('rows', $rows);
408
409
		$this->getTemplate()->add('columns', $this->getColumns());
410
		$this->getTemplate()->add('actions', $this->actions);
411
		$this->getTemplate()->add('exports', $this->exports);
412
		$this->getTemplate()->add('filters', $this->filters);
413
414
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
415
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
416
		$this->getTemplate()->add('icon_prefix', static::$icon_prefix);
417
		$this->getTemplate()->add('items_detail', $this->items_detail);
418
		$this->getTemplate()->add('columns_visibility', $this->columns_visibility);
419
		$this->getTemplate()->add('columnsSummary', $this->columnsSummary);
420
421
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
422
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
423
424
		/**
425
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
426
		 */
427
		$this->getTemplate()->add('filter', $this['filter']);
428
429
		/**
430
		 * Set template file and render it
431
		 */
432
		$this->getTemplate()->setFile($this->getTemplateFile());
433
		$this->getTemplate()->render();
434
	}
435
436
437
	/********************************************************************************
438
	 *                                 ROW CALLBACK                                 *
439
	 ********************************************************************************/
440
441
442
	/**
443
	 * Each row can be modified with user callback
444
	 * @param  callable  $callback
445
	 * @return static
446
	 */
447
	public function setRowCallback(callable $callback)
448
	{
449
		$this->rowCallback = $callback;
450
451
		return $this;
452
	}
453
454
455
	/********************************************************************************
456
	 *                                 DATA SOURCE                                  *
457
	 ********************************************************************************/
458
459
460
	/**
461
	 * By default ID, you can change that
462
	 * @param string $primary_key
463
	 * @return static
464
	 */
465
	public function setPrimaryKey($primary_key)
466
	{
467
		if ($this->dataModel instanceof DataModel) {
468
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
469
		}
470
471
		$this->primary_key = $primary_key;
472
473
		return $this;
474
	}
475
476
477
	/**
478
	 * Set Grid data source
479
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
480
	 * @return static
481
	 */
482
	public function setDataSource($source)
483
	{
484
		$this->dataModel = new DataModel($source, $this->primary_key);
485
486
		return $this;
487
	}
488
489
490
	/********************************************************************************
491
	 *                                  TEMPLATING                                  *
492
	 ********************************************************************************/
493
494
495
	/**
496
	 * Set custom template file to render
497
	 * @param string $template_file
498
	 * @return static
499
	 */
500
	public function setTemplateFile($template_file)
501
	{
502
		$this->template_file = $template_file;
503
504
		return $this;
505
	}
506
507
508
	/**
509
	 * Get DataGrid template file
510
	 * @return string
511
	 * @return static
512
	 */
513
	public function getTemplateFile()
514
	{
515
		return $this->template_file ?: $this->getOriginalTemplateFile();
516
	}
517
518
519
	/**
520
	 * Get DataGrid original template file
521
	 * @return string
522
	 */
523
	public function getOriginalTemplateFile()
524
	{
525
		return __DIR__.'/templates/datagrid.latte';
526
	}
527
528
529
	/**
530
	 * Tell datagrid wheteher to use or not happy components
531
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
532
	 * @return void|bool
533
	 */
534
	public function useHappyComponents($use = NULL)
535
	{
536
		if (NULL === $use) {
537
			return $this->use_happy_components;
538
		}
539
540
		$this->use_happy_components = (bool) $use;
541
	}
542
543
544
	/********************************************************************************
545
	 *                                   SORTING                                    *
546
	 ********************************************************************************/
547
548
549
	/**
550
	 * Set default sorting
551
	 * @param array $sort
552
	 * @return static
553
	 */
554
	public function setDefaultSort($sort)
555
	{
556
		if (is_string($sort)) {
557
			$sort = [$sort => 'ASC'];
558
		} else {
559
			$sort = (array) $sort;
560
		}
561
562
		$this->default_sort = $sort;
563
564
		return $this;
565
	}
566
567
568
	/**
569
	 * User may set default sorting, apply it
570
	 * @return void
571
	 */
572
	public function findDefaultSort()
573
	{
574
		if (!empty($this->sort)) {
575
			return;
576
		}
577
578
		if (!empty($this->default_sort)) {
579
			$this->sort = $this->default_sort;
580
		}
581
582
		$this->saveSessionData('_grid_sort', $this->sort);
583
	}
584
585
586
	/**
587
	 * Set grido to be sortable
588
	 * @param bool $sortable
589
	 * @return static
590
	 */
591
	public function setSortable($sortable = TRUE)
592
	{
593
		if ($this->getItemsDetail()) {
594
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
595
		}
596
597
		$this->sortable = (bool) $sortable;
598
599
		return $this;
600
	}
601
602
603
	/**
604
	 * Set sortable handle
605
	 * @param string $handler
606
	 * @return static
607
	 */
608
	public function setSortableHandler($handler = 'sort!')
609
	{
610
		$this->sortable_handler = (string) $handler;
611
612
		return $this;
613
	}
614
615
616
	/**
617
	 * Tell whether DataGrid is sortable
618
	 * @return bool
619
	 */
620
	public function isSortable()
621
	{
622
		return $this->sortable;
623
	}
624
625
	/**
626
	 * Return sortable handle name
627
	 * @return string
628
	 */
629
	public function getSortableHandler()
630
	{
631
		return $this->sortable_handler;
632
	}
633
634
635
	/********************************************************************************
636
	 *                                  TREE VIEW                                   *
637
	 ********************************************************************************/
638
639
640
	/**
641
	 * Is tree view set?
642
	 * @return boolean
643
	 */
644
	public function isTreeView()
645
	{
646
		return (bool) $this->tree_view_children_callback;
647
	}
648
649
650
	/**
651
	 * Setting tree view
652
	 * @param callable $get_children_callback
653
	 * @param string|callable $tree_view_has_children_column
654
	 * @return static
655
	 */
656
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
657
	{
658
		if (!is_callable($get_children_callback)) {
659
			throw new DataGridException(
660
				'Parameters to method DataGrid::setTreeView must be of type callable'
661
			);
662
		}
663
664
		if (is_callable($tree_view_has_children_column)) {
665
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
666
			$tree_view_has_children_column = NULL;
667
		}
668
669
		$this->tree_view_children_callback = $get_children_callback;
670
		$this->tree_view_has_children_column = $tree_view_has_children_column;
671
672
		/**
673
		 * TUrn off pagination
674
		 */
675
		$this->setPagination(FALSE);
676
677
		/**
678
		 * Set tree view template file
679
		 */
680
		if (!$this->template_file) {
681
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
682
		}
683
684
		return $this;
685
	}
686
687
688
	/**
689
	 * Is tree view children callback set?
690
	 * @return boolean
691
	 */
692
	public function hasTreeViewChildrenCallback()
693
	{
694
		return is_callable($this->tree_view_has_children_callback);
695
	}
696
697
698
	/**
699
	 * @param  mixed $item
700
	 * @return boolean
701
	 */
702
	public function treeViewChildrenCallback($item)
703
	{
704
		return call_user_func($this->tree_view_has_children_callback, $item);
705
	}
706
707
708
	/********************************************************************************
709
	 *                                    COLUMNS                                   *
710
	 ********************************************************************************/
711
712
713
	/**
714
	 * Add text column with no other formating
715
	 * @param  string      $key
716
	 * @param  string      $name
717
	 * @param  string|null $column
718
	 * @return Column\ColumnText
719
	 */
720
	public function addColumnText($key, $name, $column = NULL)
721
	{
722
		$this->addColumnCheck($key);
723
		$column = $column ?: $key;
724
725
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
726
	}
727
728
729
	/**
730
	 * Add column with link
731
	 * @param  string      $key
732
	 * @param  string      $name
733
	 * @param  string|null $column
734
	 * @return Column\ColumnLink
735
	 */
736
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
737
	{
738
		$this->addColumnCheck($key);
739
		$column = $column ?: $key;
740
		$href = $href ?: $key;
741
742
		if (NULL === $params) {
743
			$params = [$this->primary_key];
744
		}
745
746
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
747
	}
748
749
750
	/**
751
	 * Add column with possible number formating
752
	 * @param  string      $key
753
	 * @param  string      $name
754
	 * @param  string|null $column
755
	 * @return Column\ColumnNumber
756
	 */
757
	public function addColumnNumber($key, $name, $column = NULL)
758
	{
759
		$this->addColumnCheck($key);
760
		$column = $column ?: $key;
761
762
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
763
	}
764
765
766
	/**
767
	 * Add column with date formating
768
	 * @param  string      $key
769
	 * @param  string      $name
770
	 * @param  string|null $column
771
	 * @return Column\ColumnDateTime
772
	 */
773
	public function addColumnDateTime($key, $name, $column = NULL)
774
	{
775
		$this->addColumnCheck($key);
776
		$column = $column ?: $key;
777
778
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
779
	}
780
781
782
	/**
783
	 * Add column status
784
	 * @param  string      $key
785
	 * @param  string      $name
786
	 * @param  string|null $column
787
	 * @return Column\ColumnStatus
788
	 */
789
	public function addColumnStatus($key, $name, $column = NULL)
790
	{
791
		$this->addColumnCheck($key);
792
		$column = $column ?: $key;
793
794
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
795
	}
796
797
798
	/**
799
	 * @param string $key
800
	 * @param Column\Column $column
801
	 * @return Column\Column
802
	 */
803
	protected function addColumn($key, Column\Column $column)
804
	{
805
		$this->onColumnAdd($key, $column);
0 ignored issues
show
Unused Code introduced by
The call to DataGrid::onColumnAdd() has too many arguments starting with $key.

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

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

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

Loading history...
806
807
		$this->columns_visibility[$key] = [
808
			'visible' => TRUE,
809
			'name' => $column->getName()
810
		];
811
812
		return $this->columns[$key] = $column;
813
	}
814
815
816
	/**
817
	 * Return existing column
818
	 * @param  string $key
819
	 * @return Column\Column
820
	 * @throws DataGridException
821
	 */
822
	public function getColumn($key)
823
	{
824
		if (!isset($this->columns[$key])) {
825
			throw new DataGridException("There is no column at key [$key] defined.");
826
		}
827
828
		return $this->columns[$key];
829
	}
830
831
832
	/**
833
	 * Remove column
834
	 * @param string $key
835
	 * @return void
836
	 */
837
	public function removeColumn($key)
838
	{
839
		unset($this->columns[$key]);
840
	}
841
842
843
	/**
844
	 * Check whether given key already exists in $this->columns
845
	 * @param  string $key
846
	 * @throws DataGridException
847
	 */
848
	protected function addColumnCheck($key)
849
	{
850
		if (isset($this->columns[$key])) {
851
			throw new DataGridException("There is already column at key [$key] defined.");
852
		}
853
	}
854
855
856
	/********************************************************************************
857
	 *                                    ACTIONS                                   *
858
	 ********************************************************************************/
859
860
861
	/**
862
	 * Create action
863
	 * @param string     $key
864
	 * @param string     $name
865
	 * @param string     $href
866
	 * @param array|null $params
867
	 * @return Column\Action
868
	 */
869
	public function addAction($key, $name, $href = NULL, array $params = NULL)
870
	{
871
		$this->addActionCheck($key);
872
		$href = $href ?: $key;
873
874
		if (NULL === $params) {
875
			$params = [$this->primary_key];
876
		}
877
878
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
879
	}
880
881
882
	/**
883
	 * Create action callback
884
	 * @param string     $key
885
	 * @param string     $name
886
	 * @return Column\Action
887
	 */
888
	public function addActionCallback($key, $name, $callback = NULL)
889
	{
890
		$this->addActionCheck($key);
891
		$params = ['__id' => $this->primary_key];
892
893
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
894
895
		if ($callback) {
896
			if (!is_callable($callback)) {
897
				throw new DataGridException('ActionCallback callback has to be callable.');
898
			}
899
900
			$action->onClick[] = $callback;
901
		}
902
903
		return $action;
904
	}
905
906
907
	/**
908
	 * Get existing action
909
	 * @param  string       $key
910
	 * @return Column\Action
911
	 * @throws DataGridException
912
	 */
913
	public function getAction($key)
914
	{
915
		if (!isset($this->actions[$key])) {
916
			throw new DataGridException("There is no action at key [$key] defined.");
917
		}
918
919
		return $this->actions[$key];
920
	}
921
922
923
	/**
924
	 * Remove action
925
	 * @param string $key
926
	 * @return void
927
	 */
928
	public function removeAction($key)
929
	{
930
		unset($this->actions[$key]);
931
	}
932
933
934
	/**
935
	 * Check whether given key already exists in $this->filters
936
	 * @param  string $key
937
	 * @throws DataGridException
938
	 */
939
	protected function addActionCheck($key)
940
	{
941
		if (isset($this->actions[$key])) {
942
			throw new DataGridException("There is already action at key [$key] defined.");
943
		}
944
	}
945
946
947
	/********************************************************************************
948
	 *                                    FILTERS                                   *
949
	 ********************************************************************************/
950
951
952
	/**
953
	 * Add filter fot text search
954
	 * @param string       $key
955
	 * @param string       $name
956
	 * @param array|string $columns
957
	 * @return Filter\FilterText
958
	 * @throws DataGridException
959
	 */
960
	public function addFilterText($key, $name, $columns = NULL)
961
	{
962
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
963
964
		if (!is_array($columns)) {
965
			throw new DataGridException("Filter Text can except only array or string.");
966
		}
967
968
		$this->addFilterCheck($key);
969
970
		return $this->filters[$key] = new Filter\FilterText($key, $name, $columns);
971
	}
972
973
974
	/**
975
	 * Add select box filter
976
	 * @param string $key
977
	 * @param string $name
978
	 * @param array  $options
979
	 * @param string $column
980
	 * @return Filter\FilterSelect
981
	 * @throws DataGridException
982
	 */
983 View Code Duplication
	public function addFilterSelect($key, $name, array $options, $column = NULL)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
984
	{
985
		$column = $column ?: $key;
986
987
		if (!is_string($column)) {
988
			throw new DataGridException("Filter Select can only filter in one column.");
989
		}
990
991
		$this->addFilterCheck($key);
992
993
		return $this->filters[$key] = new Filter\FilterSelect($key, $name, $options, $column);
994
	}
995
996
997
	/**
998
	 * Add multi select box filter
999
	 * @param string $key
1000
	 * @param string $name
1001
	 * @param array  $options
1002
	 * @param string $column
1003
	 * @return Filter\FilterSelect
1004
	 * @throws DataGridException
1005
	 */
1006
	public function addFilterMultiSelect($key, $name, array $options, $column = NULL)
1007
	{
1008
		$column = $column ?: $key;
1009
1010
		if (!is_string($column)) {
1011
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1012
		}
1013
1014
		$this->addFilterCheck($key);
1015
1016
		return $this->filters[$key] = new Filter\FilterMultiSelect($key, $name, $options, $column);
1017
	}
1018
1019
1020
	/**
1021
	 * Add datepicker filter
1022
	 * @param string $key
1023
	 * @param string $name
1024
	 * @param string $column
1025
	 * @return Filter\FilterDate
1026
	 * @throws DataGridException
1027
	 */
1028
	public function addFilterDate($key, $name, $column = NULL)
1029
	{
1030
		$column = $column ?: $key;
1031
1032
		if (!is_string($column)) {
1033
			throw new DataGridException("FilterDate can only filter in one column.");
1034
		}
1035
1036
		$this->addFilterCheck($key);
1037
1038
		return $this->filters[$key] = new Filter\FilterDate($key, $name, $column);
1039
	}
1040
1041
1042
	/**
1043
	 * Add range filter (from - to)
1044
	 * @param string $key
1045
	 * @param string $name
1046
	 * @param string $column
1047
	 * @return Filter\FilterRange
1048
	 * @throws DataGridException
1049
	 */
1050 View Code Duplication
	public function addFilterRange($key, $name, $column = NULL, $name_second = '-')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1051
	{
1052
		$column = $column ?: $key;
1053
1054
		if (!is_string($column)) {
1055
			throw new DataGridException("FilterRange can only filter in one column.");
1056
		}
1057
1058
		$this->addFilterCheck($key);
1059
1060
		return $this->filters[$key] = new Filter\FilterRange($key, $name, $column, $name_second);
1061
	}
1062
1063
1064
	/**
1065
	 * Add datepicker filter (from - to)
1066
	 * @param string $key
1067
	 * @param string $name
1068
	 * @param string $column
1069
	 * @return Filter\FilterDateRange
1070
	 * @throws DataGridException
1071
	 */
1072 View Code Duplication
	public function addFilterDateRange($key, $name, $column = NULL, $name_second = '-')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1073
	{
1074
		$column = $column ?: $key;
1075
1076
		if (!is_string($column)) {
1077
			throw new DataGridException("FilterDateRange can only filter in one column.");
1078
		}
1079
1080
		$this->addFilterCheck($key);
1081
1082
		return $this->filters[$key] = new Filter\FilterDateRange($key, $name, $column, $name_second);
1083
	}
1084
1085
1086
	/**
1087
	 * Check whether given key already exists in $this->filters
1088
	 * @param  string $key
1089
	 * @throws DataGridException
1090
	 */
1091
	protected function addFilterCheck($key)
1092
	{
1093
		if (isset($this->filters[$key])) {
1094
			throw new DataGridException("There is already action at key [$key] defined.");
1095
		}
1096
	}
1097
1098
1099
	/**
1100
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1101
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1102
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1103
	 */
1104
	public function assableFilters()
1105
	{
1106
		foreach ($this->filter as $key => $value) {
1107
			if (!isset($this->filters[$key])) {
1108
				$this->deleteSesssionData($key);
1109
1110
				continue;
1111
			}
1112
1113
			if (is_array($value) || $value instanceof \Traversable) {
1114
				if (!ArraysHelper::testEmpty($value)) {
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type object<Traversable>; however, Ublaboo\DataGrid\Utils\ArraysHelper::testEmpty() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1115
					$this->filters[$key]->setValue($value);
1116
				}
1117
			} else {
1118
				if ($value !== '' && $value !== NULL) {
1119
					$this->filters[$key]->setValue($value);
1120
				}
1121
			}
1122
		}
1123
1124
		foreach ($this->columns as $column) {
1125
			if (isset($this->sort[$column->getSortingColumn()])) {
1126
				$column->setSort($this->sort);
1127
			}
1128
		}
1129
1130
		return $this->filters;
1131
	}
1132
1133
1134
	/**
1135
	 * Remove filter
1136
	 * @param string $key
1137
	 * @return void
1138
	 */
1139
	public function removeFilter($key)
1140
	{
1141
		unset($this->filters[$key]);
1142
	}
1143
1144
1145
	/**
1146
	 * Get defined filter
1147
	 * @param  string $key
1148
	 * @return Filter\Filter
1149
	 */
1150
	public function getFilter($key)
1151
	{
1152
		if (!isset($this->filters[$key])) {
1153
			throw new DataGridException("Filter [{$key}] is not defined");
1154
		}
1155
1156
		return $this->filters[$key];
1157
	}
1158
1159
1160
	/********************************************************************************
1161
	 *                                  FILTERING                                   *
1162
	 ********************************************************************************/
1163
1164
1165
	/**
1166
	 * Is filter active?
1167
	 * @return boolean
1168
	 */
1169
	public function isFilterActive()
1170
	{
1171
		$is_filter = ArraysHelper::testTruthy($this->filter);
1172
1173
		return ($is_filter) || $this->force_filter_active;
1174
	}
1175
1176
1177
	/**
1178
	 * Tell that filter is active from whatever reasons
1179
	 * return static
1180
	 */
1181
	public function setFilterActive()
1182
	{
1183
		$this->force_filter_active = TRUE;
1184
1185
		return $this;
1186
	}
1187
1188
1189
	/**
1190
	 * Set filter values (force - overwrite user data)
1191
	 * @param array $filter
1192
	 * @return static
1193
	 */
1194
	public function setFilter(array $filter)
1195
	{
1196
		$this->filter = $filter;
1197
1198
		$this->saveSessionData('_grid_has_filtered', 1);
1199
1200
		return $this;
1201
	}
1202
1203
1204
	/**
1205
	 * If we want to sent some initial filter
1206
	 * @param array $filter
0 ignored issues
show
Documentation introduced by
There is no parameter named $filter. Did you maybe mean $default_filter?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
1207
	 * @param bool  $use_on_reset
1208
	 * @return static
1209
	 */
1210
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1211
	{
1212
		foreach ($default_filter as $key => $value) {
1213
			$filter = $this->getFilter($key);
1214
1215
			if (!$filter) {
1216
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1217
			}
1218
1219
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1220
				throw new DataGridException(
1221
					"Default value of filter [$key] - MultiSelect has to be an array"
1222
				);
1223
			}
1224
1225
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1226
				if (!is_array($value)) {
1227
					throw new DataGridException(
1228
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1229
					);
1230
				}
1231
1232
				$temp = $value;
1233
				unset($temp['from'], $temp['to']);
1234
1235
				if (!empty($temp)) {
1236
					throw new DataGridException(
1237
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1238
					);
1239
				}
1240
			}
1241
		}
1242
1243
		$this->default_filter = $default_filter;
1244
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1245
1246
		return $this;
1247
	}
1248
1249
1250
	/**
1251
	 * User may set default filter, find it
1252
	 * @return void
1253
	 */
1254
	public function findDefaultFilter()
1255
	{
1256
		if (!empty($this->filter)) {
1257
			return;
1258
		}
1259
1260
		if ($this->getSessionData('_grid_has_filtered')) {
1261
			return;
1262
		}
1263
1264
		if (!empty($this->default_filter)) {
1265
			$this->filter = $this->default_filter;
1266
		}
1267
1268
		foreach ($this->filter as $key => $value) {
1269
			$this->saveSessionData($key, $value);
1270
		}
1271
	}
1272
1273
1274
	/**
1275
	 * FilterAndGroupAction form factory
1276
	 * @return Form
1277
	 */
1278
	public function createComponentFilter()
1279
	{
1280
		$form = new Form($this, 'filter');
1281
1282
		$form->setMethod('get');
1283
1284
		$form->setTranslator($this->getTranslator());
1285
1286
		/**
1287
		 * InlineEdit part
1288
		 */
1289
		$inline_edit_container = $form->addContainer('inline_edit');
1290
1291 View Code Duplication
		if ($this->inlineEdit instanceof InlineEdit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1292
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1293
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1294
				->setValidationScope(FALSE);
1295
1296
			$this->inlineEdit->onControlAdd($inline_edit_container);
1297
		}
1298
1299
		/**
1300
		 * InlineAdd part
1301
		 */
1302
		$inline_add_container = $form->addContainer('inline_add');
1303
1304 View Code Duplication
		if ($this->inlineAdd instanceof InlineEdit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1305
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1306
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1307
				->setValidationScope(FALSE)
1308
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1309
1310
			$this->inlineAdd->onControlAdd($inline_add_container);
1311
		}
1312
1313
		/**
1314
		 * ItemDetail form part
1315
		 */
1316
		$items_detail_form = $this->getItemDetailForm();
1317
1318
		if ($items_detail_form instanceof Nette\Forms\Container) {
1319
			$form['items_detail_form'] = $items_detail_form;
1320
		}
1321
1322
		/**
1323
		 * Filter part
1324
		 */
1325
		$filter_container = $form->addContainer('filter');
1326
1327
		foreach ($this->filters as $filter) {
1328
			$filter->addToFormContainer($filter_container);
1329
		}
1330
1331
		/**
1332
		 * Group action part
1333
		 */
1334
		$group_action_container = $form->addContainer('group_action');
1335
1336
		if ($this->hasGroupActions()) {
1337
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1338
		}
1339
1340
		$form->setDefaults(['filter' => $this->filter]);
1341
1342
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1343
1344
		return $form;
1345
	}
1346
1347
1348
	/**
1349
	 * Set $this->filter values after filter form submitted
1350
	 * @param  Form $form
1351
	 * @return void
1352
	 */
1353
	public function filterSucceeded(Form $form)
1354
	{
1355
		if ($this->snippets_set) {
1356
			return;
1357
		}
1358
1359
		$values = $form->getValues();
1360
1361
		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...
1362
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1363
				return;
1364
			}
1365
		}
1366
1367
		/**
1368
		 * Inline edit
1369
		 */
1370
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1371
			$edit = $form['inline_edit'];
1372
1373
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1374
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1375
				$primary_where_column = $form->getHttpData(
1376
					Form::DATA_LINE,
1377
					'inline_edit[_primary_where_column]'
1378
				);
1379
1380 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...
1381
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1382
1383
					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...
1384
						$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...
1385
					}
1386
				}
1387
1388
				$this->redrawItem($id, $primary_where_column);
1389
1390
				return;
1391
			}
1392
		}
1393
1394
		/**
1395
		 * Inline add
1396
		 */
1397
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1398
			$add = $form['inline_add'];
1399
1400
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1401 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...
1402
					$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...
1403
1404
					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...
1405
						$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...
1406
					}
1407
				}
1408
1409
				return;
1410
			}
1411
		}
1412
1413
		/**
1414
		 * Filter itself
1415
		 */
1416
		$values = $values['filter'];
1417
1418
		foreach ($values as $key => $value) {
1419
			/**
1420
			 * Session stuff
1421
			 */
1422
			$this->saveSessionData($key, $value);
1423
1424
			/**
1425
			 * Other stuff
1426
			 */
1427
			$this->filter[$key] = $value;
1428
		}
1429
1430
		if (!empty($values)) {
1431
			$this->saveSessionData('_grid_has_filtered', 1);
1432
		}
1433
1434
		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...
1435
			$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...
1436
1437
			foreach ($this->columns as $key => $column) {
1438
				if ($column->isSortable()) {
1439
					$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...
1440
						'sort' => $column->getSortNext()
1441
					]);
1442
				}
1443
			}
1444
		}
1445
1446
		$this->reload();
1447
	}
1448
1449
1450
	/**
1451
	 * Should be datagrid filters rendered separately?
1452
	 * @param boolean $out
1453
	 * @return static
1454
	 */
1455
	public function setOuterFilterRendering($out = TRUE)
1456
	{
1457
		$this->outer_filter_rendering = (bool) $out;
1458
1459
		return $this;
1460
	}
1461
1462
1463
	/**
1464
	 * Are datagrid filters rendered separately?
1465
	 * @return boolean
1466
	 */
1467
	public function hasOuterFilterRendering()
1468
	{
1469
		return $this->outer_filter_rendering;
1470
	}
1471
1472
1473
	/**
1474
	 * Try to restore session stuff
1475
	 * @return void
1476
	 */
1477
	public function findSessionValues()
1478
	{
1479
		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...
1480
			return;
1481
		}
1482
1483
		if (!$this->remember_state) {
1484
			return;
1485
		}
1486
1487
		if ($page = $this->getSessionData('_grid_page')) {
1488
			$this->page = $page;
1489
		}
1490
1491
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1492
			$this->per_page = $per_page;
1493
		}
1494
1495
		if ($sort = $this->getSessionData('_grid_sort')) {
1496
			$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...
1497
		}
1498
1499
		foreach ($this->getSessionData() as $key => $value) {
1500
			$other_session_keys = [
1501
				'_grid_per_page',
1502
				'_grid_sort',
1503
				'_grid_page',
1504
				'_grid_has_filtered',
1505
				'_grid_hidden_columns',
1506
				'_grid_hidden_columns_manipulated'
1507
			];
1508
1509
			if (!in_array($key, $other_session_keys)) {
1510
				$this->filter[$key] = $value;
1511
			}
1512
		}
1513
1514
		/**
1515
		 * When column is sorted via custom callback, apply it
1516
		 */
1517
		if (empty($this->sort_callback) && !empty($this->sort)) {
1518
			foreach ($this->sort as $key => $order) {
1519
				$column = $this->getColumn($key);
1520
1521
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1522
					$this->sort_callback = $column->getSortableCallback();
1523
				}
1524
			}
1525
		}
1526
	}
1527
1528
1529
	/********************************************************************************
1530
	 *                                    EXPORTS                                   *
1531
	 ********************************************************************************/
1532
1533
1534
	/**
1535
	 * Add export of type callback
1536
	 * @param string $text
1537
	 * @param callable $callback
1538
	 * @param boolean $filtered
1539
	 * @return Export\Export
1540
	 */
1541
	public function addExportCallback($text, $callback, $filtered = FALSE)
1542
	{
1543
		if (!is_callable($callback)) {
1544
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1545
		}
1546
1547
		return $this->addToExports(new Export\Export($text, $callback, $filtered));
1548
	}
1549
1550
1551
	/**
1552
	 * Add already implemented csv export
1553
	 * @param string      $text
1554
	 * @param string      $csv_file_name
1555
	 * @param string|null $output_encoding
1556
	 * @param string|null $delimiter
1557
	 * @return Export\Export
1558
	 */
1559
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1560
	{
1561
		return $this->addToExports(new Export\ExportCsv(
1562
			$text,
1563
			$csv_file_name,
1564
			FALSE,
1565
			$output_encoding,
1566
			$delimiter
1567
		));
1568
	}
1569
1570
1571
	/**
1572
	 * Add already implemented csv export, but for filtered data
1573
	 * @param string      $text
1574
	 * @param string      $csv_file_name
1575
	 * @param string|null $output_encoding
1576
	 * @param string|null $delimiter
1577
	 * @return Export\Export
1578
	 */
1579
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL)
1580
	{
1581
		return $this->addToExports(new Export\ExportCsv(
1582
			$text,
1583
			$csv_file_name,
1584
			TRUE,
1585
			$output_encoding,
1586
			$delimiter
1587
		));
1588
	}
1589
1590
1591
	/**
1592
	 * Add export to array
1593
	 * @param Export\Export $export
1594
	 * @return Export\Export
1595
	 */
1596
	protected function addToExports(Export\Export $export)
1597
	{
1598
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1599
1600
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1601
1602
		return $this->exports[$id] = $export;
1603
	}
1604
1605
1606
	public function resetExportsLinks()
1607
	{
1608
		foreach ($this->exports as $id => $export) {
1609
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1610
		}
1611
	}
1612
1613
1614
	/********************************************************************************
1615
	 *                                 GROUP ACTIONS                                *
1616
	 ********************************************************************************/
1617
1618
1619
	/**
1620
	 * Alias for add group select action
1621
	 * @param string $title
1622
	 * @param array  $options
1623
	 * @return GroupAction\GroupAction
1624
	 */
1625
	public function addGroupAction($title, $options = [])
1626
	{
1627
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1628
	}
1629
1630
	/**
1631
	 * Add group action (select box)
1632
	 * @param string $title
1633
	 * @param array  $options
1634
	 * @return GroupAction\GroupAction
1635
	 */
1636
	public function addGroupSelectAction($title, $options = [])
1637
	{
1638
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1639
	}
1640
1641
	/**
1642
	 * Add group action (text input)
1643
	 * @param string $title
1644
	 * @return GroupAction\GroupAction
1645
	 */
1646
	public function addGroupTextAction($title)
1647
	{
1648
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1649
	}
1650
1651
	/**
1652
	 * Get collection of all group actions
1653
	 * @return GroupAction\GroupActionCollection
1654
	 */
1655
	public function getGroupActionCollection()
1656
	{
1657
		if (!$this->group_action_collection) {
1658
			$this->group_action_collection = new GroupAction\GroupActionCollection();
1659
		}
1660
1661
		return $this->group_action_collection;
1662
	}
1663
1664
1665
	/**
1666
	 * Has datagrid some group actions?
1667
	 * @return boolean
1668
	 */
1669
	public function hasGroupActions()
1670
	{
1671
		return (bool) $this->group_action_collection;
1672
	}
1673
1674
1675
	/********************************************************************************
1676
	 *                                   HANDLERS                                   *
1677
	 ********************************************************************************/
1678
1679
1680
	/**
1681
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1682
	 * @param  int  $page
1683
	 * @return void
1684
	 */
1685
	public function handlePage($page)
1686
	{
1687
		/**
1688
		 * Session stuff
1689
		 */
1690
		$this->page = $page;
1691
		$this->saveSessionData('_grid_page', $page);
1692
1693
		$this->reload(['table']);
1694
	}
1695
1696
1697
	/**
1698
	 * Handler for sorting
1699
	 * @param array $sort
1700
	 * @return void
1701
	 */
1702
	public function handleSort(array $sort)
1703
	{
1704
		$new_sort = [];
1705
1706
		/**
1707
		 * Find apropirate column
1708
		 */
1709
		foreach ($sort as $key => $value) {
1710
			if (empty($this->columns[$key])) {
1711
				throw new DataGridException("Column <$key> not found");
1712
			}
1713
1714
			$column = $this->columns[$key];
1715
			$new_sort = [$column->getSortingColumn() => $value];
1716
1717
			/**
1718
			 * Pagination may be reseted after sorting
1719
			 */
1720
			if ($column->sortableResetPagination()) {
1721
				$this->page = 1;
1722
				$this->saveSessionData('_grid_page', 1);
1723
			}
1724
1725
			/**
1726
			 * Custom sorting callback may be applied
1727
			 */
1728
			if ($column->getSortableCallback()) {
1729
				$this->sort_callback = $column->getSortableCallback();
1730
			}
1731
		}
1732
1733
		/**
1734
		 * Session stuff
1735
		 */
1736
		$this->sort = $new_sort;
1737
		$this->saveSessionData('_grid_sort', $this->sort);
1738
1739
		$this->reload(['table']);
1740
	}
1741
1742
1743
	/**
1744
	 * handler for reseting the filter
1745
	 * @return void
1746
	 */
1747
	public function handleResetFilter()
1748
	{
1749
		/**
1750
		 * Session stuff
1751
		 */
1752
		$this->deleteSesssionData('_grid_page');
1753
1754
		if ($this->default_filter_use_on_reset) {
1755
			$this->deleteSesssionData('_grid_has_filtered');
1756
		}
1757
1758
		foreach ($this->getSessionData() as $key => $value) {
1759
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page', '_grid_has_filtered'])) {
1760
				$this->deleteSesssionData($key);
1761
			}
1762
		}
1763
1764
		$this->filter = [];
1765
1766
		$this->reload(['grid']);
1767
	}
1768
1769
1770
	/**
1771
	 * Handler for export
1772
	 * @param  int $id Key for particular export class in array $this->exports
1773
	 * @return void
1774
	 */
1775
	public function handleExport($id)
1776
	{
1777
		if (!isset($this->exports[$id])) {
1778
			throw new Nette\Application\ForbiddenRequestException;
1779
		}
1780
1781
		if (!empty($this->columns_export_order)) {
1782
			$this->setColumnsOrder($this->columns_export_order);
1783
		}
1784
1785
		$export = $this->exports[$id];
1786
1787
		if ($export->isFiltered()) {
1788
			$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...
1789
			$filter    = $this->assableFilters();
1790
		} else {
1791
			$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...
1792
			$filter    = [];
1793
		}
1794
1795
		if (NULL === $this->dataModel) {
1796
			throw new DataGridException('You have to set a data source first.');
1797
		}
1798
1799
		$rows = [];
1800
1801
		$items = Nette\Utils\Callback::invokeArgs(
1802
			[$this->dataModel, 'filterData'], [
1803
				NULL,
1804
				new Sorting($this->sort, $this->sort_callback),
1805
				$filter
1806
			]
1807
		);
1808
1809
		foreach ($items as $item) {
1810
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1811
		}
1812
1813
		if ($export instanceof Export\ExportCsv) {
1814
			$export->invoke($rows, $this);
1815
		} else {
1816
			$export->invoke($items, $this);
1817
		}
1818
1819
		if ($export->isAjax()) {
1820
			$this->reload();
1821
		}
1822
	}
1823
1824
1825
	/**
1826
	 * Handler for getting children of parent item (e.g. category)
1827
	 * @param  int $parent
1828
	 * @return void
1829
	 */
1830
	public function handleGetChildren($parent)
1831
	{
1832
		$this->setDataSource(
1833
			call_user_func($this->tree_view_children_callback, $parent)
1834
		);
1835
1836
		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...
1837
			$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...
1838
			$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...
1839
1840
			$this->redrawControl('items');
1841
1842
			$this->onRedraw();
1843
		} else {
1844
			$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...
1845
		}
1846
	}
1847
1848
1849
	/**
1850
	 * Handler for getting item detail
1851
	 * @param  mixed $id
1852
	 * @return void
1853
	 */
1854
	public function handleGetItemDetail($id)
1855
	{
1856
		$this->getTemplate()->add('toggle_detail', $id);
1857
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1858
1859
		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...
1860
			$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...
1861
			$this->redrawControl('items');
1862
1863
			$this->onRedraw();
1864
		} else {
1865
			$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...
1866
		}
1867
	}
1868
1869
1870
	/**
1871
	 * Handler for inline editing
1872
	 * @param  mixed $id
1873
	 * @param  mixed $key
1874
	 * @return void
1875
	 */
1876
	public function handleEdit($id, $key)
1877
	{
1878
		$column = $this->getColumn($key);
1879
		$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...
1880
1881
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1882
	}
1883
1884
1885
	/**
1886
	 * Redraw $this
1887
	 * @return void
1888
	 */
1889
	public function reload($snippets = [])
1890
	{
1891
		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...
1892
			$this->redrawControl('tbody');
1893
			$this->redrawControl('pagination');
1894
1895
			/**
1896
			 * manualy reset exports links...
1897
			 */
1898
			$this->resetExportsLinks();
1899
			$this->redrawControl('exports');
1900
1901
			foreach ($snippets as $snippet) {
1902
				$this->redrawControl($snippet);
1903
			}
1904
1905
			$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...
1906
1907
			$this->onRedraw();
1908
		} else {
1909
			$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...
1910
		}
1911
	}
1912
1913
1914
	/**
1915
	 * Handler for column status
1916
	 * @param  string $id
1917
	 * @param  string $key
1918
	 * @param  string $value
1919
	 * @return void
1920
	 */
1921
	public function handleChangeStatus($id, $key, $value)
1922
	{
1923
		if (empty($this->columns[$key])) {
1924
			throw new DataGridException("ColumnStatus[$key] does not exist");
1925
		}
1926
1927
		$this->columns[$key]->onChange($id, $value);
1928
	}
1929
1930
1931
	/**
1932
	 * Redraw just one row via ajax
1933
	 * @param  int   $id
1934
	 * @param  mixed $primary_where_column
1935
	 * @return void
1936
	 */
1937
	public function redrawItem($id, $primary_where_column = NULL)
1938
	{
1939
		$this->snippets_set = TRUE;
1940
1941
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
1942
1943
		$this->redrawControl('items');
1944
		$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...
1945
1946
		$this->onRedraw();
1947
	}
1948
1949
1950
	/**
1951
	 * Tell datagrid to display all columns
1952
	 * @return void
1953
	 */
1954
	public function handleShowAllColumns()
1955
	{
1956
		$this->deleteSesssionData('_grid_hidden_columns');
1957
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1958
1959
		$this->redrawControl();
1960
1961
		$this->onRedraw();
1962
	}
1963
1964
1965
	/**
1966
	 * Tell datagrid to display default columns
1967
	 * @return void
1968
	 */
1969
	public function handleShowDefaultColumns()
1970
	{
1971
		$this->deleteSesssionData('_grid_hidden_columns');
1972
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
1973
1974
		$this->redrawControl();
1975
1976
		$this->onRedraw();
1977
	}
1978
1979
1980
	/**
1981
	 * Reveal particular column
1982
	 * @param  string $column
1983
	 * @return void
1984
	 */
1985 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...
1986
	{
1987
		$columns = $this->getSessionData('_grid_hidden_columns');
1988
1989
		if (!empty($columns)) {
1990
			$pos = array_search($column, $columns);
1991
1992
			if ($pos !== FALSE) {
1993
				unset($columns[$pos]);
1994
			}
1995
		}
1996
1997
		$this->saveSessionData('_grid_hidden_columns', $columns);
1998
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
1999
2000
		$this->redrawControl();
2001
2002
		$this->onRedraw();
2003
	}
2004
2005
2006
	/**
2007
	 * Notice datagrid to not display particular columns
2008
	 * @param  string $column
2009
	 * @return void
2010
	 */
2011 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...
2012
	{
2013
		/**
2014
		 * Store info about hiding a column to session
2015
		 */
2016
		$columns = $this->getSessionData('_grid_hidden_columns');
2017
2018
		if (empty($columns)) {
2019
			$columns = [$column];
2020
		} else if (!in_array($column, $columns)) {
2021
			array_push($columns, $column);
2022
		}
2023
2024
		$this->saveSessionData('_grid_hidden_columns', $columns);
2025
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2026
2027
		$this->redrawControl();
2028
2029
		$this->onRedraw();
2030
	}
2031
2032
2033
	public function handleActionCallback($__key, $__id)
2034
	{
2035
		$action = $this->getAction($__key);
2036
2037
		if (!($action instanceof Column\ActionCallback)) {
2038
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2039
		}
2040
2041
		$action->onClick($__id);
2042
	}
2043
2044
2045
	/********************************************************************************
2046
	 *                                  PAGINATION                                  *
2047
	 ********************************************************************************/
2048
2049
2050
	/**
2051
	 * Set options of select "items_per_page"
2052
	 * @param array $items_per_page_list
2053
	 * @return static
2054
	 */
2055
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
2056
	{
2057
		$this->items_per_page_list = $items_per_page_list;
2058
2059
		if ($include_all) {
2060
			$this->items_per_page_list[] = 'all';
2061
		}
2062
2063
		return $this;
2064
	}
2065
2066
2067
	/**
2068
	 * Paginator factory
2069
	 * @return Components\DataGridPaginator\DataGridPaginator
2070
	 */
2071
	public function createComponentPaginator()
2072
	{
2073
		/**
2074
		 * Init paginator
2075
		 */
2076
		$component = new Components\DataGridPaginator\DataGridPaginator(
2077
			$this->getTranslator()
2078
		);
2079
		$paginator = $component->getPaginator();
2080
2081
		$paginator->setPage($this->page);
2082
		$paginator->setItemsPerPage($this->getPerPage());
2083
2084
		return $component;
2085
	}
2086
2087
2088
	/**
2089
	 * PerPage form factory
2090
	 * @return Form
2091
	 */
2092
	public function createComponentPerPage()
2093
	{
2094
		$form = new Form;
2095
2096
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
2097
			->setValue($this->getPerPage());
2098
2099
		$form->addSubmit('submit', '');
2100
2101
		$saveSessionData = [$this, 'saveSessionData'];
2102
2103
		$form->onSuccess[] = function($form, $values) use ($saveSessionData) {
2104
			/**
2105
			 * Session stuff
2106
			 */
2107
			$saveSessionData('_grid_per_page', $values->per_page);
2108
2109
			/**
2110
			 * Other stuff
2111
			 */
2112
			$this->per_page = $values->per_page;
2113
			$this->reload();
2114
		};
2115
2116
		return $form;
2117
	}
2118
2119
2120
	/**
2121
	 * Get parameter per_page
2122
	 * @return int
2123
	 */
2124
	public function getPerPage()
2125
	{
2126
		$items_per_page_list = $this->getItemsPerPageList();
2127
2128
		$per_page = $this->per_page ?: reset($items_per_page_list);
2129
2130
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2131
			$per_page = reset($items_per_page_list);
2132
		}
2133
2134
		return $per_page;
2135
	}
2136
2137
2138
	/**
2139
	 * Get associative array of items_per_page_list
2140
	 * @return array
2141
	 */
2142
	public function getItemsPerPageList()
2143
	{
2144
		if (empty($this->items_per_page_list)) {
2145
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2146
		}
2147
2148
		$list = array_flip($this->items_per_page_list);
2149
2150
		foreach ($list as $key => $value) {
2151
			$list[$key] = $key;
2152
		}
2153
2154
		if (array_key_exists('all', $list)) {
2155
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2156
		}
2157
2158
		return $list;
2159
	}
2160
2161
2162
	/**
2163
	 * Order Grid to "be paginated"
2164
	 * @param bool $do
2165
	 * @return static
2166
	 */
2167
	public function setPagination($do)
2168
	{
2169
		$this->do_paginate = (bool) $do;
2170
2171
		return $this;
2172
	}
2173
2174
2175
	/**
2176
	 * Tell whether Grid is paginated
2177
	 * @return bool
2178
	 */
2179
	public function isPaginated()
2180
	{
2181
		return $this->do_paginate;
2182
	}
2183
2184
2185
	/**
2186
	 * Return current paginator class
2187
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2188
	 */
2189
	public function getPaginator()
2190
	{
2191
		if ($this->isPaginated() && $this->per_page !== 'all') {
2192
			return $this['paginator'];
2193
		}
2194
2195
		return NULL;
2196
	}
2197
2198
2199
	/********************************************************************************
2200
	 *                                     I18N                                     *
2201
	 ********************************************************************************/
2202
2203
2204
	/**
2205
	 * Set datagrid translator
2206
	 * @param Nette\Localization\ITranslator $translator
2207
	 * @return static
2208
	 */
2209
	public function setTranslator(Nette\Localization\ITranslator $translator)
2210
	{
2211
		$this->translator = $translator;
2212
2213
		return $this;
2214
	}
2215
2216
2217
	/**
2218
	 * Get translator for datagrid
2219
	 * @return Nette\Localization\ITranslator
2220
	 */
2221
	public function getTranslator()
2222
	{
2223
		if (!$this->translator) {
2224
			$this->translator = new Localization\SimpleTranslator;
2225
		}
2226
2227
		return $this->translator;
2228
	}
2229
2230
2231
	/********************************************************************************
2232
	 *                                 COLUMNS ORDER                                *
2233
	 ********************************************************************************/
2234
2235
2236
	/**
2237
	 * Set order of datagrid columns
2238
	 * @param array $order
2239
	 * @return static
2240
	 */
2241
	public function setColumnsOrder($order)
2242
	{
2243
		$new_order = [];
2244
2245
		foreach ($order as $key) {
2246
			if (isset($this->columns[$key])) {
2247
				$new_order[$key] = $this->columns[$key];
2248
			}
2249
		}
2250
2251
		if (sizeof($new_order) === sizeof($this->columns)) {
2252
			$this->columns = $new_order;
2253
		} else {
2254
			throw new DataGridException('When changing columns order, you have to specify all columns');
2255
		}
2256
2257
		return $this;
2258
	}
2259
2260
2261
	/**
2262
	 * Columns order may be different for export and normal grid
2263
	 * @param array $order
2264
	 */
2265
	public function setColumnsExportOrder($order)
2266
	{
2267
		$this->columns_export_order = (array) $order;
2268
	}
2269
2270
2271
	/********************************************************************************
2272
	 *                                SESSION & URL                                 *
2273
	 ********************************************************************************/
2274
2275
2276
	/**
2277
	 * Find some unique session key name
2278
	 * @return string
2279
	 */
2280
	public function getSessionSectionName()
2281
	{
2282
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2283
	}
2284
2285
2286
	/**
2287
	 * Should datagrid remember its filters/pagination/etc using session?
2288
	 * @param bool $remember
2289
	 * @return static
2290
	 */
2291
	public function setRememberState($remember = TRUE)
2292
	{
2293
		$this->remember_state = (bool) $remember;
2294
2295
		return $this;
2296
	}
2297
2298
2299
	/**
2300
	 * Should datagrid refresh url using history API?
2301
	 * @param bool $refresh
2302
	 * @return static
2303
	 */
2304
	public function setRefreshUrl($refresh = TRUE)
2305
	{
2306
		$this->refresh_url = (bool) $refresh;
2307
2308
2309
		return $this;
2310
	}
2311
2312
2313
	/**
2314
	 * Get session data if functionality is enabled
2315
	 * @param  string $key
2316
	 * @return mixed
2317
	 */
2318
	public function getSessionData($key = NULL, $default_value = NULL)
2319
	{
2320
		if (!$this->remember_state) {
2321
			return $key ? $default_value : [];
2322
		}
2323
2324
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2325
	}
2326
2327
2328
	/**
2329
	 * Save session data - just if it is enabled
2330
	 * @param  string $key
2331
	 * @param  mixed  $value
2332
	 * @return void
2333
	 */
2334
	public function saveSessionData($key, $value)
2335
	{
2336
		if ($this->remember_state) {
2337
			$this->grid_session->{$key} = $value;
2338
		}
2339
	}
2340
2341
2342
	/**
2343
	 * Delete session data
2344
	 * @return void
2345
	 */
2346
	public function deleteSesssionData($key)
2347
	{
2348
		unset($this->grid_session->{$key});
2349
	}
2350
2351
2352
	/********************************************************************************
2353
	 *                                  ITEM DETAIL                                 *
2354
	 ********************************************************************************/
2355
2356
2357
	/**
2358
	 * Get items detail parameters
2359
	 * @return array
2360
	 */
2361
	public function getItemsDetail()
2362
	{
2363
		return $this->items_detail;
2364
	}
2365
2366
2367
	/**
2368
	 * Items can have thair detail - toggled
2369
	 * @param mixed $detail callable|string|bool
2370
	 * @param bool|NULL $primary_where_column
2371
	 * @return Column\ItemDetail
2372
	 */
2373
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2374
	{
2375
		if ($this->isSortable()) {
2376
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2377
		}
2378
2379
		$this->items_detail = new Column\ItemDetail(
2380
			$this,
2381
			$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...
2382
		);
2383
2384
		if (is_string($detail)) {
2385
			/**
2386
			 * Item detail will be in separate template
2387
			 */
2388
			$this->items_detail->setType('template');
2389
			$this->items_detail->setTemplate($detail);
2390
2391
		} else if (is_callable($detail)) {
2392
			/**
2393
			 * Item detail will be rendered via custom callback renderer
2394
			 */
2395
			$this->items_detail->setType('renderer');
2396
			$this->items_detail->setRenderer($detail);
2397
2398
		} else if (TRUE === $detail) {
2399
			/**
2400
			 * Item detail will be rendered probably via block #detail
2401
			 */
2402
			$this->items_detail->setType('block');
2403
2404
		} else {
2405
			throw new DataGridException(
2406
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2407
			);
2408
		}
2409
2410
		return $this->items_detail;
2411
	}
2412
2413
2414
	/**
2415
	 * @param callable $callable_set_container 
2416
	 * @return static
2417
	 */
2418
	public function setItemsDetailForm(callable $callable_set_container)
2419
	{
2420
		if ($this->items_detail instanceof Column\ItemDetail) {
2421
			$this->items_detail->setForm(
2422
				new Utils\ItemDetailForm($callable_set_container)
2423
			);
2424
2425
			return $this;
2426
		}
2427
2428
		throw new DataGridException('Please set the ItemDetail first.');
2429
	}
2430
2431
2432
	/**
2433
	 * @return Nette\Forms\Container|NULL
2434
	 */
2435
	public function getItemDetailForm()
2436
	{
2437
		if ($this->items_detail instanceof Column\ItemDetail) {
2438
			return $this->items_detail->getForm();
2439
		}
2440
2441
		return NULL;
2442
	}
2443
2444
2445
	/********************************************************************************
2446
	 *                                ROW PRIVILEGES                                *
2447
	 ********************************************************************************/
2448
2449
2450
	/**
2451
	 * @param  callable $condition
2452
	 * @return void
2453
	 */
2454
	public function allowRowsGroupAction(callable $condition)
2455
	{
2456
		$this->row_conditions['group_action'] = $condition;
2457
	}
2458
2459
2460
	/**
2461
	 * @param  string   $key
2462
	 * @param  callable $condition
2463
	 * @return void
2464
	 */
2465
	public function allowRowsAction($key, callable $condition)
2466
	{
2467
		$this->row_conditions['action'][$key] = $condition;
2468
	}
2469
2470
2471
	/**
2472
	 * @param  string      $name
2473
	 * @param  string|null $key
2474
	 * @return bool|callable
2475
	 */
2476
	public function getRowCondition($name, $key = NULL)
2477
	{
2478
		if (!isset($this->row_conditions[$name])) {
2479
			return FALSE;
2480
		}
2481
2482
		$condition = $this->row_conditions[$name];
2483
2484
		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...
2485
			return $condition;
2486
		}
2487
2488
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2489
	}
2490
2491
2492
	/********************************************************************************
2493
	 *                               COLUMN CALLBACK                                *
2494
	 ********************************************************************************/
2495
2496
2497
	/**
2498
	 * @param  string   $key
2499
	 * @param  callable $callback
2500
	 * @return void
2501
	 */
2502
	public function addColumnCallback($key, callable $callback)
2503
	{
2504
		$this->column_callbacks[$key] = $callback;
2505
	}
2506
2507
2508
	/**
2509
	 * @param  string $key
2510
	 * @return callable|null
2511
	 */
2512
	public function getColumnCallback($key)
2513
	{
2514
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2515
	}
2516
2517
2518
	/********************************************************************************
2519
	 *                                 INLINE EDIT                                  *
2520
	 ********************************************************************************/
2521
2522
2523
	/**
2524
	 * @return InlineEdit
2525
	 */
2526
	public function addInlineEdit($primary_where_column = NULL)
2527
	{
2528
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2529
2530
		return $this->inlineEdit;
2531
	}
2532
2533
2534
	/**
2535
	 * @return InlineEdit|null
2536
	 */
2537
	public function getInlineEdit()
2538
	{
2539
		return $this->inlineEdit;
2540
	}
2541
2542
2543
	/**
2544
	 * @param  mixed $id
2545
	 * @return void
2546
	 */
2547
	public function handleInlineEdit($id)
2548
	{
2549
		if ($this->inlineEdit) {
2550
			$this->inlineEdit->setItemId($id);
2551
2552
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2553
2554
			$this['filter']['inline_edit']->addHidden('_id', $id);
2555
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2556
2557
			$this->redrawItem($id, $primary_where_column);
2558
		}
2559
	}
2560
2561
2562
	/********************************************************************************
2563
	 *                                  INLINE ADD                                  *
2564
	 ********************************************************************************/
2565
2566
2567
	/**
2568
	 * @return InlineEdit
2569
	 */
2570
	public function addInlineAdd()
2571
	{
2572
		$this->inlineAdd = new InlineEdit($this);
2573
2574
		$this->inlineAdd
2575
			->setIcon('plus')
2576
			->setClass('btn btn-xs btn-default');
2577
2578
		return $this->inlineAdd;
2579
	}
2580
2581
2582
	/**
2583
	 * @return InlineEdit|null
2584
	 */
2585
	public function getInlineAdd()
2586
	{
2587
		return $this->inlineAdd;
2588
	}
2589
2590
2591
	/********************************************************************************
2592
	 *                               HIDEABLE COLUMNS                               *
2593
	 ********************************************************************************/
2594
2595
2596
	/**
2597
	 * Can datagrid hide colums?
2598
	 * @return boolean
2599
	 */
2600
	public function canHideColumns()
2601
	{
2602
		return (bool) $this->can_hide_columns;
2603
	}
2604
2605
2606
	/**
2607
	 * Order Grid to set columns hideable.
2608
	 * @return static
2609
	 */
2610
	public function setColumnsHideable()
2611
	{
2612
		$this->can_hide_columns = TRUE;
2613
2614
		return $this;
2615
	}
2616
2617
2618
	/********************************************************************************
2619
	 *                                COLUMNS SUMMARY                               *
2620
	 ********************************************************************************/
2621
2622
2623
	/**
2624
	 * Will datagrid show summary in the end?
2625
	 * @return bool
2626
	 */
2627
	public function hasColumnsSummary()
2628
	{
2629
		return $this->columnsSummary instanceof ColumnsSummary;
2630
	}
2631
2632
2633
	/**
2634
	 * Set columns to be summarized in the end.
2635
	 * @param  array  $columns
2636
	 * @return ColumnsSummary
2637
	 */
2638
	public function setColumnsSummary(array $columns)
2639
	{
2640
		$this->columnsSummary = new ColumnsSummary($this, $columns);
2641
2642
		return $this->columnsSummary;
2643
	}
2644
2645
2646
	/**
2647
	 * @return ColumnsSummary|NULL
2648
	 */
2649
	public function getColumnsSummary()
2650
	{
2651
		return $this->columnsSummary;
2652
	}
2653
2654
2655
	/********************************************************************************
2656
	 *                                   INTERNAL                                   *
2657
	 ********************************************************************************/
2658
2659
2660
	/**
2661
	 * Get count of columns
2662
	 * @return int
2663
	 */
2664
	public function getColumnsCount()
2665
	{
2666
		$count = sizeof($this->getColumns());
2667
2668
		if (!empty($this->actions)
2669
			|| $this->isSortable()
2670
			|| $this->getItemsDetail()
2671
			|| $this->getInlineEdit()
2672
			|| $this->getInlineAdd()) {
2673
			$count++;
2674
		}
2675
2676
		if ($this->hasGroupActions()) {
2677
			$count++;
2678
		}
2679
2680
		return $count;
2681
	}
2682
2683
2684
	/**
2685
	 * Get primary key of datagrid data source
2686
	 * @return string
2687
	 */
2688
	public function getPrimaryKey()
2689
	{
2690
		return $this->primary_key;
2691
	}
2692
2693
2694
	/**
2695
	 * Get set of set columns
2696
	 * @return Column\IColumn[]
2697
	 */
2698
	public function getColumns()
2699
	{
2700
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2701
			$columns_to_hide = [];
2702
2703
			foreach ($this->columns as $key => $column) {
2704
				if ($column->getDefaultHide()) {
2705
					$columns_to_hide[] = $key;
2706
				}
2707
			}
2708
2709
			if (!empty($columns_to_hide)) {
2710
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2711
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2712
			}
2713
		}
2714
2715
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2716
		
2717
		foreach ($hidden_columns as $column) {
2718
			if (!empty($this->columns[$column])) {
2719
				$this->columns_visibility[$column] = [
2720
					'visible' => FALSE,
2721
					'name' => $this->columns[$column]->getName()
2722
				];
2723
2724
				$this->removeColumn($column);
2725
			}
2726
		}
2727
2728
		return $this->columns;
2729
	}
2730
2731
2732
	/**
2733
	 * @return PresenterComponent
2734
	 */
2735
	public function getParent()
2736
	{
2737
		$parent = parent::getParent();
2738
2739
		if (!($parent instanceof PresenterComponent)) {
2740
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2741
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2742
			);
2743
		}
2744
2745
		return $parent;
2746
	}
2747
2748
2749
	/**
2750
	 * Some of datagrid columns is hidden by default
2751
	 * @param bool $default_hide
2752
	 */
2753
	public function setSomeColumnDefaultHide($default_hide)
2754
	{
2755
		$this->some_column_default_hide = $default_hide;
2756
	}
2757
2758
2759
	/**
2760
	 * Are some of columns hidden bydefault?
2761
	 */
2762
	public function hasSomeColumnDefaultHide()
2763
	{
2764
		return $this->some_column_default_hide;
2765
	}
2766
2767
}
2768