Completed
Pull Request — master (#451)
by
unknown
09:30
created

src/DataGrid.php (16 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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