Completed
Push — master ( 187ec0...0faac0 )
by Martin
03:36
created

DataGrid::deleteSessionData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
405
			}
406
		}
407
	}
408
409
410
	/********************************************************************************
411
	 *                                  RENDERING                                   *
412
	 ********************************************************************************/
413
414
415
	/**
416
	 * Render template
417
	 * @return void
418
	 */
419
	public function render()
420
	{
421
		/**
422
		 * Check whether datagrid has set some columns, initiated data source, etc
423
		 */
424
		if (!($this->dataModel instanceof DataModel)) {
425
			throw new DataGridException('You have to set a data source first.');
426
		}
427
428
		if (empty($this->columns)) {
429
			throw new DataGridException('You have to add at least one column.');
430
		}
431
432
		$this->getTemplate()->setTranslator($this->getTranslator());
433
434
		/**
435
		 * Invoke possible events
436
		 */
437
		$this->onRender($this);
0 ignored issues
show
Unused Code introduced by
The call to DataGrid::onRender() has too many arguments starting with $this.

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

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

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

Loading history...
438
439
		/**
440
		 * Prepare data for rendering (datagrid may render just one item)
441
		 */
442
		$rows = [];
443
444
		if (!empty($this->redraw_item)) {
445
			$items = $this->dataModel->filterRow($this->redraw_item);
446
		} else {
447
			$items = Nette\Utils\Callback::invokeArgs(
448
				[$this->dataModel, 'filterData'],
449
				[
450
					$this->getPaginator(),
451
					$this->createSorting($this->sort, $this->sort_callback),
452
					$this->assableFilters()
453
				]
454
			);
455
		}
456
457
		$callback = $this->rowCallback ?: NULL;
458
		$hasGroupActionOnRows = FALSE;
459
460
		foreach ($items as $item) {
461
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
462
463
			if (!$hasGroupActionOnRows && $row->hasGroupAction()){
464
				$hasGroupActionOnRows = TRUE;
465
			}
466
			
467
			if ($callback) {
468
				$callback($item, $row->getControl());
469
			}
470
471
			/**
472
			 * Walkaround for item snippet - snippet is the <tr> element and its class has to be also updated
473
			 */
474
			if (!empty($this->redraw_item)) {
475
				$this->getPresenter()->payload->_datagrid_redraw_item_class = $row->getControlClass();
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
476
				$this->getPresenter()->payload->_datagrid_redraw_item_id = $row->getId();
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
477
			}
478
		}
479
480
		if ($hasGroupActionOnRows){
481
			$hasGroupActionOnRows = $this->hasGroupActions();
482
		}
483
484
		if ($this->isTreeView()) {
485
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
486
		}
487
488
		$this->getTemplate()->add('rows', $rows);
489
490
		$this->getTemplate()->add('columns', $this->getColumns());
491
		$this->getTemplate()->add('actions', $this->actions);
492
		$this->getTemplate()->add('exports', $this->exports);
493
		$this->getTemplate()->add('filters', $this->filters);
494
		$this->getTemplate()->add('toolbar_buttons', $this->toolbar_buttons);
495
496
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
497
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
498
		//$this->getTemplate()->add('icon_prefix', static::$icon_prefix);
0 ignored issues
show
Unused Code Comprehensibility introduced by
82% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
499
		$this->getTemplate()->icon_prefix = static::$icon_prefix;
0 ignored issues
show
Bug introduced by
Accessing icon_prefix on the interface Nette\Application\UI\ITemplate 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...
500
		$this->getTemplate()->add('items_detail', $this->items_detail);
501
		$this->getTemplate()->add('columns_visibility', $this->getColumnsVisibility());
502
		$this->getTemplate()->add('columnsSummary', $this->columnsSummary);
503
504
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
505
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
506
507
		$this->getTemplate()->add('hasGroupActionOnRows', $hasGroupActionOnRows);
508
509
		/**
510
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
511
		 */
512
		$this->getTemplate()->add('filter', $this['filter']);
513
514
		/**
515
		 * Set template file and render it
516
		 */
517
		$this->getTemplate()->setFile($this->getTemplateFile());
518
		$this->getTemplate()->render();
519
	}
520
521
522
	/********************************************************************************
523
	 *                                 ROW CALLBACK                                 *
524
	 ********************************************************************************/
525
526
527
	/**
528
	 * Each row can be modified with user callback
529
	 * @param  callable  $callback
530
	 * @return static
531
	 */
532
	public function setRowCallback(callable $callback)
533
	{
534
		$this->rowCallback = $callback;
535
536
		return $this;
537
	}
538
539
540
	/********************************************************************************
541
	 *                                 DATA SOURCE                                  *
542
	 ********************************************************************************/
543
544
545
	/**
546
	 * By default ID, you can change that
547
	 * @param string $primary_key
548
	 * @return static
549
	 */
550
	public function setPrimaryKey($primary_key)
551
	{
552
		if ($this->dataModel instanceof DataModel) {
553
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
554
		}
555
556
		$this->primary_key = $primary_key;
557
558
		return $this;
559
	}
560
561
562
	/**
563
	 * Set Grid data source
564
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
565
	 * @return static
566
	 */
567
	public function setDataSource($source)
568
	{
569
		$this->dataModel = new DataModel($source, $this->primary_key);
570
571
		return $this;
572
	}
573
574
575
	/**
576
	 * @return DataSource\IDataSource|NULL
577
	 */
578
	public function getDataSource()
579
	{
580
		if (!$this->dataModel) {
581
			return NULL;
582
		}
583
584
		return $this->dataModel->getDataSource();
585
	}
586
587
588
	/********************************************************************************
589
	 *                                  TEMPLATING                                  *
590
	 ********************************************************************************/
591
592
593
	/**
594
	 * Set custom template file to render
595
	 * @param string $template_file
596
	 * @return static
597
	 */
598
	public function setTemplateFile($template_file)
599
	{
600
		$this->template_file = $template_file;
601
602
		return $this;
603
	}
604
605
606
	/**
607
	 * Get DataGrid template file
608
	 * @return string
609
	 * @return static
610
	 */
611
	public function getTemplateFile()
612
	{
613
		return $this->template_file ?: $this->getOriginalTemplateFile();
614
	}
615
616
617
	/**
618
	 * Get DataGrid original template file
619
	 * @return string
620
	 */
621
	public function getOriginalTemplateFile()
622
	{
623
		return __DIR__.'/templates/datagrid.latte';
624
	}
625
626
627
	/**
628
	 * Tell datagrid wheteher to use or not happy components
629
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
630
	 * @return void|bool
631
	 */
632
	public function useHappyComponents($use = NULL)
633
	{
634
		if (NULL === $use) {
635
			return $this->use_happy_components;
636
		}
637
638
		$this->use_happy_components = (bool) $use;
639
	}
640
641
642
	/********************************************************************************
643
	 *                                   SORTING                                    *
644
	 ********************************************************************************/
645
646
647
	/**
648
	 * Set default sorting
649
	 * @param array $sort
650
	 * @return static
651
	 */
652
	public function setDefaultSort($sort)
653
	{
654
		if (is_string($sort)) {
655
			$sort = [$sort => 'ASC'];
656
		} else {
657
			$sort = (array) $sort;
658
		}
659
660
		$this->default_sort = $sort;
661
662
		return $this;
663
	}
664
665
666
	/**
667
	 * User may set default sorting, apply it
668
	 * @return void
669
	 */
670
	public function findDefaultSort()
671
	{
672
		if (!empty($this->sort)) {
673
			return;
674
		}
675
676
		if (!empty($this->default_sort)) {
677
			$this->sort = $this->default_sort;
678
		}
679
680
		$this->saveSessionData('_grid_sort', $this->sort);
681
	}
682
683
684
	/**
685
	 * Set grido to be sortable
686
	 * @param bool $sortable
687
	 * @return static
688
	 */
689
	public function setSortable($sortable = TRUE)
690
	{
691
		if ($this->getItemsDetail()) {
692
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
693
		}
694
695
		$this->sortable = (bool) $sortable;
696
697
		return $this;
698
	}
699
700
701
	/**
702
	 * Set sortable handle
703
	 * @param string $handler
704
	 * @return static
705
	 */
706
	public function setSortableHandler($handler = 'sort!')
707
	{
708
		$this->sortable_handler = (string) $handler;
709
710
		return $this;
711
	}
712
713
714
	/**
715
	 * Tell whether DataGrid is sortable
716
	 * @return bool
717
	 */
718
	public function isSortable()
719
	{
720
		return $this->sortable;
721
	}
722
723
	/**
724
	 * Return sortable handle name
725
	 * @return string
726
	 */
727
	public function getSortableHandler()
728
	{
729
		return $this->sortable_handler;
730
	}
731
732
733
	/**
734
	 * @param  array         $sort
735
	 * @param  callable|NULL $sort_callback
736
	 * @return void
737
	 */
738
	protected function createSorting(array $sort, $sort_callback)
739
	{
740
		foreach ($sort as $key => $order) {
741
			unset($sort[$key]);
742
743
			try {
744
				$column = $this->getColumn($key);
745
746
			} catch (DataGridColumnNotFoundException $e) {
747
				continue;
748
			}
749
750
			$sort[$column->getSortingColumn()] = $order;
751
		}
752
753
		// required for first request
754
		if (isset($column) && $sort_callback === NULL) {
755
			$sort_callback = $column->getSortableCallback();
756
		}
757
758
		return new Sorting($sort, $sort_callback);
759
	}
760
761
762
	/********************************************************************************
763
	 *                                  TREE VIEW                                   *
764
	 ********************************************************************************/
765
766
767
	/**
768
	 * Is tree view set?
769
	 * @return boolean
770
	 */
771
	public function isTreeView()
772
	{
773
		return (bool) $this->tree_view_children_callback;
774
	}
775
776
777
	/**
778
	 * Setting tree view
779
	 * @param callable $get_children_callback
780
	 * @param string|callable $tree_view_has_children_column
781
	 * @return static
782
	 */
783
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
784
	{
785
		if (!is_callable($get_children_callback)) {
786
			throw new DataGridException(
787
				'Parameters to method DataGrid::setTreeView must be of type callable'
788
			);
789
		}
790
791
		if (is_callable($tree_view_has_children_column)) {
792
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
793
			$tree_view_has_children_column = NULL;
794
		}
795
796
		$this->tree_view_children_callback = $get_children_callback;
797
		$this->tree_view_has_children_column = $tree_view_has_children_column;
798
799
		/**
800
		 * TUrn off pagination
801
		 */
802
		$this->setPagination(FALSE);
803
804
		/**
805
		 * Set tree view template file
806
		 */
807
		if (!$this->template_file) {
808
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
809
		}
810
811
		return $this;
812
	}
813
814
815
	/**
816
	 * Is tree view children callback set?
817
	 * @return boolean
818
	 */
819
	public function hasTreeViewChildrenCallback()
820
	{
821
		return is_callable($this->tree_view_has_children_callback);
822
	}
823
824
825
	/**
826
	 * @param  mixed $item
827
	 * @return boolean
828
	 */
829
	public function treeViewChildrenCallback($item)
830
	{
831
		return call_user_func($this->tree_view_has_children_callback, $item);
832
	}
833
834
835
	/********************************************************************************
836
	 *                                    COLUMNS                                   *
837
	 ********************************************************************************/
838
839
840
	/**
841
	 * Add text column with no other formating
842
	 * @param  string      $key
843
	 * @param  string      $name
844
	 * @param  string|null $column
845
	 * @return Column\ColumnText
846
	 */
847
	public function addColumnText($key, $name, $column = NULL)
848
	{
849
		$this->addColumnCheck($key);
850
		$column = $column ?: $key;
851
852
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
853
	}
854
855
856
	/**
857
	 * Add column with link
858
	 * @param  string      $key
859
	 * @param  string      $name
860
	 * @param  string|null $column
861
	 * @return Column\ColumnLink
862
	 */
863
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
864
	{
865
		$this->addColumnCheck($key);
866
		$column = $column ?: $key;
867
		$href = $href ?: $key;
868
869
		if (NULL === $params) {
870
			$params = [$this->primary_key];
871
		}
872
873
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
874
	}
875
876
877
	/**
878
	 * Add column with possible number formating
879
	 * @param  string      $key
880
	 * @param  string      $name
881
	 * @param  string|null $column
882
	 * @return Column\ColumnNumber
883
	 */
884
	public function addColumnNumber($key, $name, $column = NULL)
885
	{
886
		$this->addColumnCheck($key);
887
		$column = $column ?: $key;
888
889
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
890
	}
891
892
893
	/**
894
	 * Add column with date formating
895
	 * @param  string      $key
896
	 * @param  string      $name
897
	 * @param  string|null $column
898
	 * @return Column\ColumnDateTime
899
	 */
900
	public function addColumnDateTime($key, $name, $column = NULL)
901
	{
902
		$this->addColumnCheck($key);
903
		$column = $column ?: $key;
904
905
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
906
	}
907
908
909
	/**
910
	 * Add column status
911
	 * @param  string      $key
912
	 * @param  string      $name
913
	 * @param  string|null $column
914
	 * @return Column\ColumnStatus
915
	 */
916
	public function addColumnStatus($key, $name, $column = NULL)
917
	{
918
		$this->addColumnCheck($key);
919
		$column = $column ?: $key;
920
921
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
922
	}
923
924
925
	/**
926
	 * @param string $key
927
	 * @param Column\Column $column
928
	 * @return Column\Column
929
	 */
930
	protected function addColumn($key, Column\Column $column)
931
	{
932
		$this->onColumnAdd($key, $column);
0 ignored issues
show
Unused Code introduced by
The call to DataGrid::onColumnAdd() has too many arguments starting with $key.

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

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

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

Loading history...
933
934
		$this->columns_visibility[$key] = [
935
			'visible' => TRUE
936
		];
937
938
		return $this->columns[$key] = $column;
939
	}
940
941
942
	/**
943
	 * Return existing column
944
	 * @param  string $key
945
	 * @return Column\Column
946
	 * @throws DataGridException
947
	 */
948
	public function getColumn($key)
949
	{
950
		if (!isset($this->columns[$key])) {
951
			throw new DataGridColumnNotFoundException("There is no column at key [$key] defined.");
952
		}
953
954
		return $this->columns[$key];
955
	}
956
957
958
	/**
959
	 * Remove column
960
	 * @param string $key
961
	 * @return void
962
	 */
963
	public function removeColumn($key)
964
	{
965
		unset($this->columns_visibility[$key]);
966
		unset($this->columns[$key]);
967
	}
968
969
970
	/**
971
	 * Check whether given key already exists in $this->columns
972
	 * @param  string $key
973
	 * @throws DataGridException
974
	 */
975
	protected function addColumnCheck($key)
976
	{
977
		if (isset($this->columns[$key])) {
978
			throw new DataGridException("There is already column at key [$key] defined.");
979
		}
980
	}
981
982
983
	/********************************************************************************
984
	 *                                    ACTIONS                                   *
985
	 ********************************************************************************/
986
987
988
	/**
989
	 * Create action
990
	 * @param string     $key
991
	 * @param string     $name
992
	 * @param string     $href
993
	 * @param array|null $params
994
	 * @return Column\Action
995
	 */
996
	public function addAction($key, $name, $href = NULL, array $params = NULL)
997
	{
998
		$this->addActionCheck($key);
999
		$href = $href ?: $key;
1000
1001
		if (NULL === $params) {
1002
			$params = [$this->primary_key];
1003
		}
1004
1005
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
1006
	}
1007
1008
1009
	/**
1010
	 * Create action callback
1011
	 * @param string     $key
1012
	 * @param string     $name
1013
	 * @return Column\Action
1014
	 */
1015
	public function addActionCallback($key, $name, $callback = NULL)
1016
	{
1017
		$this->addActionCheck($key);
1018
		$params = ['__id' => $this->primary_key];
1019
1020
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
1021
1022
		if ($callback) {
1023
			if (!is_callable($callback)) {
1024
				throw new DataGridException('ActionCallback callback has to be callable.');
1025
			}
1026
1027
			$action->onClick[] = $callback;
1028
		}
1029
1030
		return $action;
1031
	}
1032
1033
1034
	/**
1035
	 * Get existing action
1036
	 * @param  string       $key
1037
	 * @return Column\Action
1038
	 * @throws DataGridException
1039
	 */
1040
	public function getAction($key)
1041
	{
1042
		if (!isset($this->actions[$key])) {
1043
			throw new DataGridException("There is no action at key [$key] defined.");
1044
		}
1045
1046
		return $this->actions[$key];
1047
	}
1048
1049
1050
	/**
1051
	 * Remove action
1052
	 * @param string $key
1053
	 * @return void
1054
	 */
1055
	public function removeAction($key)
1056
	{
1057
		unset($this->actions[$key]);
1058
	}
1059
1060
1061
	/**
1062
	 * Check whether given key already exists in $this->filters
1063
	 * @param  string $key
1064
	 * @throws DataGridException
1065
	 */
1066
	protected function addActionCheck($key)
1067
	{
1068
		if (isset($this->actions[$key])) {
1069
			throw new DataGridException("There is already action at key [$key] defined.");
1070
		}
1071
	}
1072
1073
1074
	/********************************************************************************
1075
	 *                                    FILTERS                                   *
1076
	 ********************************************************************************/
1077
1078
1079
	/**
1080
	 * Add filter fot text search
1081
	 * @param string       $key
1082
	 * @param string       $name
1083
	 * @param array|string $columns
1084
	 * @return Filter\FilterText
1085
	 * @throws DataGridException
1086
	 */
1087
	public function addFilterText($key, $name, $columns = NULL)
1088
	{
1089
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
1090
1091
		if (!is_array($columns)) {
1092
			throw new DataGridException("Filter Text can accept only array or string.");
1093
		}
1094
1095
		$this->addFilterCheck($key);
1096
1097
		return $this->filters[$key] = new Filter\FilterText($this, $key, $name, $columns);
1098
	}
1099
1100
1101
	/**
1102
	 * Add select box filter
1103
	 * @param string $key
1104
	 * @param string $name
1105
	 * @param array  $options
1106
	 * @param string $column
1107
	 * @return Filter\FilterSelect
1108
	 * @throws DataGridException
1109
	 */
1110 View Code Duplication
	public function addFilterSelect($key, $name, array $options, $column = NULL)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1111
	{
1112
		$column = $column ?: $key;
1113
1114
		if (!is_string($column)) {
1115
			throw new DataGridException("Filter Select can only filter in one column.");
1116
		}
1117
1118
		$this->addFilterCheck($key);
1119
1120
		return $this->filters[$key] = new Filter\FilterSelect($this, $key, $name, $options, $column);
1121
	}
1122
1123
1124
	/**
1125
	 * Add multi select box filter
1126
	 * @param string $key
1127
	 * @param string $name
1128
	 * @param array  $options
1129
	 * @param string $column
1130
	 * @return Filter\FilterSelect
1131
	 * @throws DataGridException
1132
	 */
1133 View Code Duplication
	public function addFilterMultiSelect($key, $name, array $options, $column = NULL)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1134
	{
1135
		$column = $column ?: $key;
1136
1137
		if (!is_string($column)) {
1138
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1139
		}
1140
1141
		$this->addFilterCheck($key);
1142
1143
		return $this->filters[$key] = new Filter\FilterMultiSelect($this, $key, $name, $options, $column);
1144
	}
1145
1146
1147
	/**
1148
	 * Add datepicker filter
1149
	 * @param string $key
1150
	 * @param string $name
1151
	 * @param string $column
1152
	 * @return Filter\FilterDate
1153
	 * @throws DataGridException
1154
	 */
1155 View Code Duplication
	public function addFilterDate($key, $name, $column = NULL)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1156
	{
1157
		$column = $column ?: $key;
1158
1159
		if (!is_string($column)) {
1160
			throw new DataGridException("FilterDate can only filter in one column.");
1161
		}
1162
1163
		$this->addFilterCheck($key);
1164
1165
		return $this->filters[$key] = new Filter\FilterDate($this, $key, $name, $column);
1166
	}
1167
1168
1169
	/**
1170
	 * Add range filter (from - to)
1171
	 * @param string $key
1172
	 * @param string $name
1173
	 * @param string $column
1174
	 * @return Filter\FilterRange
1175
	 * @throws DataGridException
1176
	 */
1177 View Code Duplication
	public function addFilterRange($key, $name, $column = NULL, $name_second = '-')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1178
	{
1179
		$column = $column ?: $key;
1180
1181
		if (!is_string($column)) {
1182
			throw new DataGridException("FilterRange can only filter in one column.");
1183
		}
1184
1185
		$this->addFilterCheck($key);
1186
1187
		return $this->filters[$key] = new Filter\FilterRange($this, $key, $name, $column, $name_second);
1188
	}
1189
1190
1191
	/**
1192
	 * Add datepicker filter (from - to)
1193
	 * @param string $key
1194
	 * @param string $name
1195
	 * @param string $column
1196
	 * @return Filter\FilterDateRange
1197
	 * @throws DataGridException
1198
	 */
1199 View Code Duplication
	public function addFilterDateRange($key, $name, $column = NULL, $name_second = '-')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1200
	{
1201
		$column = $column ?: $key;
1202
1203
		if (!is_string($column)) {
1204
			throw new DataGridException("FilterDateRange can only filter in one column.");
1205
		}
1206
1207
		$this->addFilterCheck($key);
1208
1209
		return $this->filters[$key] = new Filter\FilterDateRange($this, $key, $name, $column, $name_second);
1210
	}
1211
1212
1213
	/**
1214
	 * Check whether given key already exists in $this->filters
1215
	 * @param  string $key
1216
	 * @throws DataGridException
1217
	 */
1218
	protected function addFilterCheck($key)
1219
	{
1220
		if (isset($this->filters[$key])) {
1221
			throw new DataGridException("There is already action at key [$key] defined.");
1222
		}
1223
	}
1224
1225
1226
	/**
1227
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1228
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1229
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1230
	 */
1231
	public function assableFilters()
1232
	{
1233
		foreach ($this->filter as $key => $value) {
1234
			if (!isset($this->filters[$key])) {
1235
				$this->deleteSessionData($key);
1236
1237
				continue;
1238
			}
1239
1240
			if (is_array($value) || $value instanceof \Traversable) {
1241
				if (!ArraysHelper::testEmpty($value)) {
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type object<Traversable>; however, Ublaboo\DataGrid\Utils\ArraysHelper::testEmpty() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1242
					$this->filters[$key]->setValue($value);
1243
				}
1244
			} else {
1245
				if ($value !== '' && $value !== NULL) {
1246
					$this->filters[$key]->setValue($value);
1247
				}
1248
			}
1249
		}
1250
1251
		foreach ($this->columns as $key => $column) {
1252
			if (isset($this->sort[$key])) {
1253
				$column->setSort($this->sort);
1254
			}
1255
		}
1256
1257
		/**
1258
		 * Invoke possible events
1259
		 */
1260
		$this->onFiltersAssabled($this->filters);
0 ignored issues
show
Documentation Bug introduced by
The method onFiltersAssabled does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1261
1262
		return $this->filters;
1263
	}
1264
1265
1266
	/**
1267
	 * Remove filter
1268
	 * @param string $key
1269
	 * @return void
1270
	 */
1271
	public function removeFilter($key)
1272
	{
1273
		unset($this->filters[$key]);
1274
	}
1275
1276
1277
	/**
1278
	 * Get defined filter
1279
	 * @param  string $key
1280
	 * @return Filter\Filter
1281
	 */
1282
	public function getFilter($key)
1283
	{
1284
		if (!isset($this->filters[$key])) {
1285
			throw new DataGridException("Filter [{$key}] is not defined");
1286
		}
1287
1288
		return $this->filters[$key];
1289
	}
1290
1291
1292
	/**
1293
	 * @param bool $strict
1294
	 */
1295
	public function setStrictSessionFilterValues($strict = TRUE)
1296
	{
1297
		$this->strict_session_filter_values = (bool) $strict;
1298
	}
1299
1300
1301
	/********************************************************************************
1302
	 *                                  FILTERING                                   *
1303
	 ********************************************************************************/
1304
1305
1306
	/**
1307
	 * Is filter active?
1308
	 * @return boolean
1309
	 */
1310
	public function isFilterActive()
1311
	{
1312
		$is_filter = ArraysHelper::testTruthy($this->filter);
1313
1314
		return ($is_filter) || $this->force_filter_active;
1315
	}
1316
1317
1318
	/**
1319
	 * Tell that filter is active from whatever reasons
1320
	 * return static
1321
	 */
1322
	public function setFilterActive()
1323
	{
1324
		$this->force_filter_active = TRUE;
1325
1326
		return $this;
1327
	}
1328
1329
1330
	/**
1331
	 * Set filter values (force - overwrite user data)
1332
	 * @param array $filter
1333
	 * @return static
1334
	 */
1335
	public function setFilter(array $filter)
1336
	{
1337
		$this->filter = $filter;
1338
1339
		$this->saveSessionData('_grid_has_filtered', 1);
1340
1341
		return $this;
1342
	}
1343
1344
1345
	/**
1346
	 * If we want to sent some initial filter
1347
	 * @param array $filter
0 ignored issues
show
Documentation introduced by
There is no parameter named $filter. Did you maybe mean $default_filter?

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

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

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

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

Loading history...
1348
	 * @param bool  $use_on_reset
1349
	 * @return static
1350
	 */
1351
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1352
	{
1353
		foreach ($default_filter as $key => $value) {
1354
			$filter = $this->getFilter($key);
1355
1356
			if (!$filter) {
1357
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1358
			}
1359
1360
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1361
				throw new DataGridException(
1362
					"Default value of filter [$key] - MultiSelect has to be an array"
1363
				);
1364
			}
1365
1366
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1367
				if (!is_array($value)) {
1368
					throw new DataGridException(
1369
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1370
					);
1371
				}
1372
1373
				$temp = $value;
1374
				unset($temp['from'], $temp['to']);
1375
1376
				if (!empty($temp)) {
1377
					throw new DataGridException(
1378
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1379
					);
1380
				}
1381
			}
1382
		}
1383
1384
		$this->default_filter = $default_filter;
1385
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1386
1387
		return $this;
1388
	}
1389
1390
1391
	/**
1392
	 * User may set default filter, find it
1393
	 * @return void
1394
	 */
1395
	public function findDefaultFilter()
1396
	{
1397
		if (!empty($this->filter)) {
1398
			return;
1399
		}
1400
1401
		if ($this->getSessionData('_grid_has_filtered')) {
1402
			return;
1403
		}
1404
1405
		if (!empty($this->default_filter)) {
1406
			$this->filter = $this->default_filter;
1407
		}
1408
1409
		foreach ($this->filter as $key => $value) {
1410
			$this->saveSessionData($key, $value);
1411
		}
1412
	}
1413
1414
1415
	/**
1416
	 * FilterAndGroupAction form factory
1417
	 * @return Form
1418
	 */
1419
	public function createComponentFilter()
1420
	{
1421
		$form = new Form($this, 'filter');
1422
1423
		$form->setMethod(static::$form_method);
1424
1425
		$form->setTranslator($this->getTranslator());
1426
1427
		/**
1428
		 * InlineEdit part
1429
		 */
1430
		$inline_edit_container = $form->addContainer('inline_edit');
1431
1432 View Code Duplication
		if ($this->inlineEdit instanceof InlineEdit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1433
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1434
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1435
				->setValidationScope(FALSE);
1436
1437
			$this->inlineEdit->onControlAdd($inline_edit_container);
1438
			$this->inlineEdit->onControlAfterAdd($inline_edit_container);
1439
		}
1440
1441
		/**
1442
		 * InlineAdd part
1443
		 */
1444
		$inline_add_container = $form->addContainer('inline_add');
1445
1446 View Code Duplication
		if ($this->inlineAdd instanceof InlineEdit) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1447
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1448
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1449
				->setValidationScope(FALSE)
1450
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1451
1452
			$this->inlineAdd->onControlAdd($inline_add_container);
1453
			$this->inlineAdd->onControlAfterAdd($inline_add_container);
1454
		}
1455
1456
		/**
1457
		 * ItemDetail form part
1458
		 */
1459
		$items_detail_form = $this->getItemDetailForm();
1460
1461
		if ($items_detail_form instanceof Nette\Forms\Container) {
1462
			$form['items_detail_form'] = $items_detail_form;
1463
		}
1464
1465
		/**
1466
		 * Filter part
1467
		 */
1468
		$filter_container = $form->addContainer('filter');
1469
1470
		foreach ($this->filters as $filter) {
1471
			$filter->addToFormContainer($filter_container);
1472
		}
1473
1474
		if (!$this->hasAutoSubmit()) {
1475
			$filter_container['submit'] = $this->getFilterSubmitButton();
1476
		}
1477
1478
		/**
1479
		 * Group action part
1480
		 */
1481
		$group_action_container = $form->addContainer('group_action');
1482
1483
		if ($this->hasGroupActions()) {
1484
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1485
		}
1486
1487
		if (!$form->isSubmitted()) {
1488
			$this->setFilterContainerDefaults($form['filter'], $this->filter);
1489
		}
1490
1491
		/**
1492
		 * Per page part
1493
		 */
1494
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1495
			->setTranslator(NULL);
1496
1497
		if (!$form->isSubmitted()) {
1498
			$form['per_page']->setValue($this->getPerPage());
1499
		}
1500
1501
		$form->addSubmit('per_page_submit', '');
1502
		
1503
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1504
	}
1505
1506
1507
	public function setFilterContainerDefaults(Nette\Forms\Container $container, array $values)
1508
	{
1509
		foreach ($container->getComponents() as $name => $control) {
1510
			if ($control instanceof Nette\Forms\IControl) {
1511 View Code Duplication
				if (array_key_exists($name, $values)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1512
					try {
1513
						$control->setValue($values[$name]);
1514
					} catch (Nette\InvalidArgumentException $e) {
1515
						if ($this->strict_session_filter_values) {
1516
							throw $e;
1517
						}
1518
					}
1519
				}
1520
1521
			} elseif ($control instanceof Nette\Forms\Container) {
1522 View Code Duplication
				if (array_key_exists($name, $values)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1523
					try {
1524
						$control->setValues($values[$name]);
1525
					} catch (Nette\InvalidArgumentException $e) {
1526
						if ($this->strict_session_filter_values) {
1527
							throw $e;
1528
						}
1529
					}
1530
				}
1531
			}
1532
		}
1533
	}
1534
1535
1536
	/**
1537
	 * Set $this->filter values after filter form submitted
1538
	 * @param  Form $form
1539
	 * @return void
1540
	 */
1541
	public function filterSucceeded(Form $form)
1542
	{
1543
		if ($this->snippets_set) {
1544
			return;
1545
		}
1546
1547
		$values = $form->getValues();
1548
1549
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1550
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1551
				return;
1552
			}
1553
		}
1554
1555
		/**
1556
		 * Per page
1557
		 */
1558
		$this->saveSessionData('_grid_per_page', $values->per_page);
1559
		$this->per_page = $values->per_page;
1560
1561
		/**
1562
		 * Inline edit
1563
		 */
1564
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1565
			$edit = $form['inline_edit'];
1566
1567
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1568
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1569
				$primary_where_column = $form->getHttpData(
1570
					Form::DATA_LINE,
1571
					'inline_edit[_primary_where_column]'
1572
				);
1573
1574
				if ($edit['submit']->isSubmittedBy()) {
1575
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1576
					$this->getPresenter()->payload->_datagrid_inline_edited = $id;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1577
					$this->getPresenter()->payload->_datagrid_name = $this->getName();
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1578
				} else {
1579
					$this->getPresenter()->payload->_datagrid_inline_edit_cancel = $id;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1580
					$this->getPresenter()->payload->_datagrid_name = $this->getName();
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1581
				}
1582
1583
				if ($edit['submit']->isSubmittedBy() && !empty($this->inlineEdit->onCustomRedraw)) {
1584
					$this->inlineEdit->onCustomRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onCustomRedraw does not exist on object<Ublaboo\DataGrid\InlineEdit\InlineEdit>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1585
				} else {
1586
					$this->redrawItem($id, $primary_where_column);
1587
				}
1588
1589
				return;
1590
			}
1591
		}
1592
1593
		/**
1594
		 * Inline add
1595
		 */
1596
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1597
			$add = $form['inline_add'];
1598
1599
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1600
				if ($add['submit']->isSubmittedBy()) {
1601
					$this->inlineAdd->onSubmit($values->inline_add);
0 ignored issues
show
Bug introduced by
The call to onSubmit() misses a required argument $values.

This check looks for function calls that miss required arguments.

Loading history...
1602
1603
					if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1604
						$this->getPresenter()->payload->_datagrid_inline_added = TRUE;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1605
					}
1606
				}
1607
1608
				return;
1609
			}
1610
		}
1611
1612
		/**
1613
		 * Filter itself
1614
		 */
1615
		$values = $values['filter'];
1616
1617
		foreach ($values as $key => $value) {
1618
			/**
1619
			 * Session stuff
1620
			 */
1621
			$this->saveSessionData($key, $value);
1622
1623
			/**
1624
			 * Other stuff
1625
			 */
1626
			$this->filter[$key] = $value;
1627
		}
1628
1629
		if (!empty($values)) {
1630
			$this->saveSessionData('_grid_has_filtered', 1);
1631
		}
1632
1633
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1634
			$this->getPresenter()->payload->_datagrid_sort = [];
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1635
1636
			foreach ($this->columns as $key => $column) {
1637
				if ($column->isSortable()) {
1638
					$this->getPresenter()->payload->_datagrid_sort[$key] = $this->link('sort!', [
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1639
						'sort' => $column->getSortNext()
1640
					]);
1641
				}
1642
			}
1643
		}
1644
1645
		$this->reload();
1646
	}
1647
1648
1649
	/**
1650
	 * Should be datagrid filters rendered separately?
1651
	 * @param boolean $out
1652
	 * @return static
1653
	 */
1654
	public function setOuterFilterRendering($out = TRUE)
1655
	{
1656
		$this->outer_filter_rendering = (bool) $out;
1657
1658
		return $this;
1659
	}
1660
1661
1662
	/**
1663
	 * Are datagrid filters rendered separately?
1664
	 * @return boolean
1665
	 */
1666
	public function hasOuterFilterRendering()
1667
	{
1668
		return $this->outer_filter_rendering;
1669
	}
1670
1671
1672
	/**
1673
	 * Try to restore session stuff
1674
	 * @return void
1675
	 * @throws DataGridFilterNotFoundException
1676
	 */
1677
	public function findSessionValues()
1678
	{
1679
		if ($this->filter || ($this->page != 1) || !empty($this->sort) || $this->per_page) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->filter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1680
			return;
1681
		}
1682
1683
		if (!$this->remember_state) {
1684
			return;
1685
		}
1686
1687
		if ($page = $this->getSessionData('_grid_page')) {
1688
			$this->page = $page;
1689
		}
1690
1691
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1692
			$this->per_page = $per_page;
1693
		}
1694
1695
		if ($sort = $this->getSessionData('_grid_sort')) {
1696
			$this->sort = $sort;
0 ignored issues
show
Documentation Bug introduced by
It seems like $sort of type * is incompatible with the declared type array of property $sort.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1697
		}
1698
1699
		foreach ($this->getSessionData() as $key => $value) {
1700
			$other_session_keys = [
1701
				'_grid_per_page',
1702
				'_grid_sort',
1703
				'_grid_page',
1704
				'_grid_has_filtered',
1705
				'_grid_hidden_columns',
1706
				'_grid_hidden_columns_manipulated'
1707
			];
1708
1709
			if (!in_array($key, $other_session_keys)) {
1710
				try {
1711
					$this->getFilter($key);
1712
1713
					$this->filter[$key] = $value;
1714
1715
				} catch (DataGridException $e) {
1716
					if ($this->strict_session_filter_values) {
1717
						throw new DataGridFilterNotFoundException("Session filter: Filter [$key] not found");
1718
					}
1719
				}
1720
			}
1721
		}
1722
1723
		/**
1724
		 * When column is sorted via custom callback, apply it
1725
		 */
1726
		if (empty($this->sort_callback) && !empty($this->sort)) {
1727
			foreach ($this->sort as $key => $order) {
1728
				try {
1729
					$column = $this->getColumn($key);
1730
1731
				} catch (DataGridColumnNotFoundException $e) {
1732
					$this->deleteSessionData('_grid_sort');
1733
					$this->sort = [];
1734
1735
					return;
1736
				}
1737
1738
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1739
					$this->sort_callback = $column->getSortableCallback();
1740
				}
1741
			}
1742
		}
1743
	}
1744
1745
1746
	/********************************************************************************
1747
	 *                                    EXPORTS                                   *
1748
	 ********************************************************************************/
1749
1750
1751
	/**
1752
	 * Add export of type callback
1753
	 * @param string $text
1754
	 * @param callable $callback
1755
	 * @param boolean $filtered
1756
	 * @return Export\Export
1757
	 */
1758
	public function addExportCallback($text, $callback, $filtered = FALSE)
1759
	{
1760
		if (!is_callable($callback)) {
1761
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1762
		}
1763
1764
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1765
	}
1766
1767
1768
	/**
1769
	 * Add already implemented csv export
1770
	 * @param string $text
1771
	 * @param string $csv_file_name
1772
	 * @param string|null $output_encoding
1773
	 * @param string|null $delimiter
1774
	 * @param bool $include_bom
1775
	 * @return Export\Export
1776
	 */
1777 View Code Duplication
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1778
	{
1779
		return $this->addToExports(new Export\ExportCsv(
1780
			$this,
1781
			$text,
1782
			$csv_file_name,
1783
			FALSE,
1784
			$output_encoding,
1785
			$delimiter,
1786
			$include_bom
1787
		));
1788
	}
1789
1790
1791
	/**
1792
	 * Add already implemented csv export, but for filtered data
1793
	 * @param string $text
1794
	 * @param string $csv_file_name
1795
	 * @param string|null $output_encoding
1796
	 * @param string|null $delimiter
1797
	 * @param bool $include_bom
1798
	 * @return Export\Export
1799
	 */
1800 View Code Duplication
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1801
	{
1802
		return $this->addToExports(new Export\ExportCsv(
1803
			$this,
1804
			$text,
1805
			$csv_file_name,
1806
			TRUE,
1807
			$output_encoding,
1808
			$delimiter,
1809
			$include_bom
1810
		));
1811
	}
1812
1813
1814
	/**
1815
	 * Add export to array
1816
	 * @param Export\Export $export
1817
	 * @return Export\Export
1818
	 */
1819
	protected function addToExports(Export\Export $export)
1820
	{
1821
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1822
1823
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1824
1825
		return $this->exports[$id] = $export;
1826
	}
1827
1828
1829
	public function resetExportsLinks()
1830
	{
1831
		foreach ($this->exports as $id => $export) {
1832
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1833
		}
1834
	}
1835
1836
1837
	/********************************************************************************
1838
	 *                                TOOLBAR BUTTONS                               *
1839
	 ********************************************************************************/
1840
1841
1842
	/**
1843
	 * Add toolbar button
1844
	 * @param string $href
1845
	 * @param string $text
1846
	 * @param array  $params
1847
	 * @return ToolbarButton
1848
	 */
1849
	public function addToolbarButton($href, $text = '', $params = [])
1850
	{
1851
		$button = new ToolbarButton($this, $href, $text, $params);
1852
1853
		return $this->toolbar_buttons[] = $button;
1854
	}
1855
1856
1857
	/********************************************************************************
1858
	 *                                 GROUP ACTIONS                                *
1859
	 ********************************************************************************/
1860
1861
1862
	/**
1863
	 * Alias for add group select action
1864
	 * @param string $title
1865
	 * @param array  $options
1866
	 * @return GroupAction\GroupAction
1867
	 */
1868
	public function addGroupAction($title, $options = [])
1869
	{
1870
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1871
	}
1872
1873
1874
	/**
1875
	 * Add group action (select box)
1876
	 * @param string $title
1877
	 * @param array  $options
1878
	 * @return GroupAction\GroupAction
1879
	 */
1880
	public function addGroupSelectAction($title, $options = [])
1881
	{
1882
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1883
	}
1884
1885
1886
	/**
1887
	 * Add group action (text input)
1888
	 * @param string $title
1889
	 * @return GroupAction\GroupAction
1890
	 */
1891
	public function addGroupTextAction($title)
1892
	{
1893
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1894
	}
1895
1896
1897
	/**
1898
	 * Add group action (textarea)
1899
	 * @param string $title
1900
	 * @return GroupAction\GroupAction
1901
	 */
1902
	public function addGroupTextareaAction($title)
1903
	{
1904
		return $this->getGroupActionCollection()->addGroupTextareaAction($title);
1905
	}
1906
1907
1908
	/**
1909
	 * Get collection of all group actions
1910
	 * @return GroupAction\GroupActionCollection
1911
	 */
1912
	public function getGroupActionCollection()
1913
	{
1914
		if (!$this->group_action_collection) {
1915
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1916
		}
1917
1918
		return $this->group_action_collection;
1919
	}
1920
1921
1922
	/**
1923
	 * Has datagrid some group actions?
1924
	 * @return boolean
1925
	 */
1926
	public function hasGroupActions()
1927
	{
1928
		return (bool) $this->group_action_collection;
1929
	}
1930
1931
1932
	/********************************************************************************
1933
	 *                                   HANDLERS                                   *
1934
	 ********************************************************************************/
1935
1936
1937
	/**
1938
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1939
	 * @param  int  $page
1940
	 * @return void
1941
	 */
1942
	public function handlePage($page)
1943
	{
1944
		/**
1945
		 * Session stuff
1946
		 */
1947
		$this->page = $page;
1948
		$this->saveSessionData('_grid_page', $page);
1949
1950
		$this->reload(['table']);
1951
	}
1952
1953
1954
	/**
1955
	 * Handler for sorting
1956
	 * @param array $sort
1957
	 * @return void
1958
	 */
1959
	public function handleSort(array $sort)
1960
	{
1961
		$new_sort = [];
1962
1963
		/**
1964
		 * Find apropirate column
1965
		 */
1966
		foreach ($sort as $key => $value) {
1967
			if (empty($this->columns[$key])) {
1968
				throw new DataGridException("Column <$key> not found");
1969
			}
1970
1971
			$column = $this->columns[$key];
1972
			$new_sort = [$key => $value];
1973
1974
			/**
1975
			 * Pagination may be reseted after sorting
1976
			 */
1977
			if ($column->sortableResetPagination()) {
1978
				$this->page = 1;
1979
				$this->saveSessionData('_grid_page', 1);
1980
			}
1981
1982
			/**
1983
			 * Custom sorting callback may be applied
1984
			 */
1985
			if ($column->getSortableCallback()) {
1986
				$this->sort_callback = $column->getSortableCallback();
1987
			}
1988
		}
1989
1990
		/**
1991
		 * Session stuff
1992
		 */
1993
		$this->sort = $new_sort;
1994
		$this->saveSessionData('_grid_sort', $this->sort);
1995
1996
		$this->reload(['table']);
1997
	}
1998
1999
2000
	/**
2001
	 * Handler for reseting the filter
2002
	 * @return void
2003
	 */
2004
	public function handleResetFilter()
2005
	{
2006
		/**
2007
		 * Session stuff
2008
		 */
2009
		$this->deleteSessionData('_grid_page');
2010
2011
		if ($this->default_filter_use_on_reset) {
2012
			$this->deleteSessionData('_grid_has_filtered');
2013
		}
2014
2015
		foreach ($this->getSessionData() as $key => $value) {
2016
			if (!in_array($key, [
2017
				'_grid_per_page',
2018
				'_grid_sort',
2019
				'_grid_page',
2020
				'_grid_has_filtered',
2021
				'_grid_hidden_columns',
2022
				'_grid_hidden_columns_manipulated'
2023
				])) {
2024
2025
				$this->deleteSessionData($key);
2026
			}
2027
		}
2028
2029
		$this->filter = [];
2030
2031
		$this->reload(['grid']);
2032
	}
2033
2034
2035
	/**
2036
	 * @param  string $key
2037
	 * @return void
2038
	 */
2039
	public function handleResetColumnFilter($key)
2040
	{
2041
		$this->deleteSessionData($key);
2042
		unset($this->filter[$key]);
2043
2044
		$this->reload(['grid']);
2045
	}
2046
2047
2048
	/**
2049
	 * @param bool $reset
2050
	 * @return static
2051
	 */
2052
	public function setColumnReset($reset = TRUE)
2053
	{
2054
		$this->has_column_reset = (bool) $reset;
2055
2056
		return $this;
2057
	}
2058
2059
2060
	/**
2061
	 * @return bool
2062
	 */
2063
	public function hasColumnReset()
2064
	{
2065
		return $this->has_column_reset;
2066
	}
2067
2068
2069
	/**
2070
	 * @param  Filter\Filter[] $filters
2071
	 * @return void
2072
	 */
2073
	public function sendNonEmptyFiltersInPayload($filters)
2074
	{
2075
		if (!$this->hasColumnReset()) {
2076
			return;
2077
		}
2078
2079
		$non_empty_filters = [];
2080
2081
		foreach ($filters as $filter) {
2082
			if ($filter->isValueSet()) {
2083
				$non_empty_filters[] = $filter->getKey();
2084
			}
2085
		}
2086
2087
		$this->getPresenter()->payload->non_empty_filters = $non_empty_filters;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2088
	}
2089
2090
2091
	/**
2092
	 * Handler for export
2093
	 * @param  int $id Key for particular export class in array $this->exports
2094
	 * @return void
2095
	 */
2096
	public function handleExport($id)
2097
	{
2098
		if (!isset($this->exports[$id])) {
2099
			throw new Nette\Application\ForbiddenRequestException;
2100
		}
2101
2102
		if (!empty($this->columns_export_order)) {
2103
			$this->setColumnsOrder($this->columns_export_order);
2104
		}
2105
2106
		$export = $this->exports[$id];
2107
2108
		/**
2109
		 * Invoke possible events
2110
		 */
2111
		$this->onExport($this);
0 ignored issues
show
Documentation Bug introduced by
The method onExport does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
2112
2113
		if ($export->isFiltered()) {
2114
			$sort      = $this->sort;
0 ignored issues
show
Unused Code introduced by
$sort is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2115
			$filter    = $this->assableFilters();
2116
		} else {
2117
			$sort      = [$this->primary_key => 'ASC'];
0 ignored issues
show
Unused Code introduced by
$sort is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2118
			$filter    = [];
2119
		}
2120
2121
		if (NULL === $this->dataModel) {
2122
			throw new DataGridException('You have to set a data source first.');
2123
		}
2124
2125
		$rows = [];
2126
2127
		$items = Nette\Utils\Callback::invokeArgs(
2128
			[$this->dataModel, 'filterData'], [
2129
				NULL,
2130
				$this->createSorting($this->sort, $this->sort_callback),
2131
				$filter
2132
			]
2133
		);
2134
2135
		foreach ($items as $item) {
2136
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
2137
		}
2138
2139
		if ($export instanceof Export\ExportCsv) {
2140
			$export->invoke($rows);
2141
		} else {
2142
			$export->invoke($items);
2143
		}
2144
2145
		if ($export->isAjax()) {
2146
			$this->reload();
2147
		}
2148
	}
2149
2150
2151
	/**
2152
	 * Handler for getting children of parent item (e.g. category)
2153
	 * @param  int $parent
2154
	 * @return void
2155
	 */
2156
	public function handleGetChildren($parent)
2157
	{
2158
		$this->setDataSource(
2159
			call_user_func($this->tree_view_children_callback, $parent)
2160
		);
2161
2162
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2163
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2164
			$this->getPresenter()->payload->_datagrid_tree = $parent;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2165
2166
			$this->redrawControl('items');
2167
2168
			$this->onRedraw();
2169
		} else {
2170
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\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...
2171
		}
2172
	}
2173
2174
2175
	/**
2176
	 * Handler for getting item detail
2177
	 * @param  mixed $id
2178
	 * @return void
2179
	 */
2180
	public function handleGetItemDetail($id)
2181
	{
2182
		$this->getTemplate()->add('toggle_detail', $id);
2183
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
2184
2185
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2186
			$this->getPresenter()->payload->_datagrid_toggle_detail = $id;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2187
			$this->redrawControl('items');
2188
2189
			/**
2190
			 * Only for nette 2.4
2191
			 */
2192
			if (method_exists($this->getTemplate()->getLatte(), 'addProvider')) {
2193
				$this->redrawControl('gridSnippets');
2194
			}
2195
2196
			$this->onRedraw();
2197
		} else {
2198
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\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...
2199
		}
2200
	}
2201
2202
2203
	/**
2204
	 * Handler for inline editing
2205
	 * @param  mixed $id
2206
	 * @param  mixed $key
2207
	 * @return void
2208
	 */
2209
	public function handleEdit($id, $key)
2210
	{
2211
		$column = $this->getColumn($key);
2212
		$value = $this->getPresenter()->getRequest()->getPost('value');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method getRequest() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2213
2214
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
2215
	}
2216
2217
2218
	/**
2219
	 * Redraw $this
2220
	 * @return void
2221
	 */
2222
	public function reload($snippets = [])
2223
	{
2224
		if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2225
			$this->redrawControl('tbody');
2226
			$this->redrawControl('pagination');
2227
2228
			/**
2229
			 * manualy reset exports links...
2230
			 */
2231
			$this->resetExportsLinks();
2232
			$this->redrawControl('exports');
2233
2234
			foreach ($snippets as $snippet) {
2235
				$this->redrawControl($snippet);
2236
			}
2237
2238
			$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2239
			$this->getPresenter()->payload->_datagrid_name = $this->getName();
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2240
2241
			$this->onRedraw();
2242
		} else {
2243
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\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...
2244
		}
2245
	}
2246
2247
2248
	/**
2249
	 * Handler for column status
2250
	 * @param  string $id
2251
	 * @param  string $key
2252
	 * @param  string $value
2253
	 * @return void
2254
	 */
2255
	public function handleChangeStatus($id, $key, $value)
2256
	{
2257
		if (empty($this->columns[$key])) {
2258
			throw new DataGridException("ColumnStatus[$key] does not exist");
2259
		}
2260
2261
		$this->columns[$key]->onChange($id, $value);
2262
	}
2263
2264
2265
	/**
2266
	 * Redraw just one row via ajax
2267
	 * @param  int   $id
2268
	 * @param  mixed $primary_where_column
2269
	 * @return void
2270
	 */
2271
	public function redrawItem($id, $primary_where_column = NULL)
2272
	{
2273
		$this->snippets_set = TRUE;
2274
2275
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
2276
2277
		$this->redrawControl('items');
2278
2279
		$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2280
2281
		$this->onRedraw();
2282
	}
2283
2284
2285
	/**
2286
	 * Tell datagrid to display all columns
2287
	 * @return void
2288
	 */
2289
	public function handleShowAllColumns()
2290
	{
2291
		$this->deleteSessionData('_grid_hidden_columns');
2292
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2293
2294
		$this->redrawControl();
2295
2296
		$this->onRedraw();
2297
	}
2298
2299
2300
	/**
2301
	 * Tell datagrid to display default columns
2302
	 * @return void
2303
	 */
2304
	public function handleShowDefaultColumns()
2305
	{
2306
		$this->deleteSessionData('_grid_hidden_columns');
2307
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
2308
2309
		$this->redrawControl();
2310
2311
		$this->onRedraw();
2312
	}
2313
2314
2315
	/**
2316
	 * Reveal particular column
2317
	 * @param  string $column
2318
	 * @return void
2319
	 */
2320 View Code Duplication
	public function handleShowColumn($column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2321
	{
2322
		$columns = $this->getSessionData('_grid_hidden_columns');
2323
2324
		if (!empty($columns)) {
2325
			$pos = array_search($column, $columns);
2326
2327
			if ($pos !== FALSE) {
2328
				unset($columns[$pos]);
2329
			}
2330
		}
2331
2332
		$this->saveSessionData('_grid_hidden_columns', $columns);
2333
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2334
2335
		$this->redrawControl();
2336
2337
		$this->onRedraw();
2338
	}
2339
2340
2341
	/**
2342
	 * Notice datagrid to not display particular columns
2343
	 * @param  string $column
2344
	 * @return void
2345
	 */
2346 View Code Duplication
	public function handleHideColumn($column)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
2347
	{
2348
		/**
2349
		 * Store info about hiding a column to session
2350
		 */
2351
		$columns = $this->getSessionData('_grid_hidden_columns');
2352
2353
		if (empty($columns)) {
2354
			$columns = [$column];
2355
		} else if (!in_array($column, $columns)) {
2356
			array_push($columns, $column);
2357
		}
2358
2359
		$this->saveSessionData('_grid_hidden_columns', $columns);
2360
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2361
2362
		$this->redrawControl();
2363
2364
		$this->onRedraw();
2365
	}
2366
2367
2368
	public function handleActionCallback($__key, $__id)
2369
	{
2370
		$action = $this->getAction($__key);
2371
2372
		if (!($action instanceof Column\ActionCallback)) {
2373
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2374
		}
2375
2376
		$action->onClick($__id);
2377
	}
2378
2379
2380
	/********************************************************************************
2381
	 *                                  PAGINATION                                  *
2382
	 ********************************************************************************/
2383
2384
2385
	/**
2386
	 * Set options of select "items_per_page"
2387
	 * @param array $items_per_page_list
2388
	 * @return static
2389
	 */
2390
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
2391
	{
2392
		$this->items_per_page_list = $items_per_page_list;
2393
2394
		if ($include_all) {
2395
			$this->items_per_page_list[] = 'all';
2396
		}
2397
2398
		return $this;
2399
	}
2400
2401
2402
	/**
2403
	 * Set default "items per page" value in pagination select
2404
	 * @param $count
2405
	 * @return static
2406
	 */
2407
	public function setDefaultPerPage($count)
2408
	{
2409
		$this->default_per_page = $count;
2410
2411
		return $this;
2412
	}
2413
2414
2415
	/**
2416
	 * User may set default "items per page" value, apply it
2417
	 * @return void
2418
	 */
2419
	public function findDefaultPerPage()
2420
	{
2421
		if (!empty($this->per_page)) {
2422
			return;
2423
		}
2424
2425
		if (!empty($this->default_per_page)) {
2426
			$this->per_page = $this->default_per_page;
2427
		}
2428
2429
		$this->saveSessionData('_grid_per_page', $this->per_page);
2430
	}
2431
2432
2433
	/**
2434
	 * Paginator factory
2435
	 * @return Components\DataGridPaginator\DataGridPaginator
2436
	 */
2437
	public function createComponentPaginator()
2438
	{
2439
		/**
2440
		 * Init paginator
2441
		 */
2442
		$component = new Components\DataGridPaginator\DataGridPaginator(
2443
			$this->getTranslator(),
2444
			static::$icon_prefix
2445
		);
2446
		$paginator = $component->getPaginator();
2447
2448
		$paginator->setPage($this->page);
2449
		$paginator->setItemsPerPage($this->getPerPage());
2450
2451
		return $component;
2452
	}
2453
2454
2455
	/**
2456
	 * Get parameter per_page
2457
	 * @return int
2458
	 */
2459
	public function getPerPage()
2460
	{
2461
		$items_per_page_list = $this->getItemsPerPageList();
2462
2463
		$per_page = $this->per_page ?: reset($items_per_page_list);
2464
2465
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2466
			$per_page = reset($items_per_page_list);
2467
		}
2468
2469
		return $per_page;
2470
	}
2471
2472
2473
	/**
2474
	 * Get associative array of items_per_page_list
2475
	 * @return array
2476
	 */
2477
	public function getItemsPerPageList()
2478
	{
2479
		if (empty($this->items_per_page_list)) {
2480
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2481
		}
2482
2483
		$list = array_flip($this->items_per_page_list);
2484
2485
		foreach ($list as $key => $value) {
2486
			$list[$key] = $key;
2487
		}
2488
2489
		if (array_key_exists('all', $list)) {
2490
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2491
		}
2492
2493
		return $list;
2494
	}
2495
2496
2497
	/**
2498
	 * Order Grid to "be paginated"
2499
	 * @param bool $do
2500
	 * @return static
2501
	 */
2502
	public function setPagination($do)
2503
	{
2504
		$this->do_paginate = (bool) $do;
2505
2506
		return $this;
2507
	}
2508
2509
2510
	/**
2511
	 * Tell whether Grid is paginated
2512
	 * @return bool
2513
	 */
2514
	public function isPaginated()
2515
	{
2516
		return $this->do_paginate;
2517
	}
2518
2519
2520
	/**
2521
	 * Return current paginator class
2522
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2523
	 */
2524
	public function getPaginator()
2525
	{
2526
		if ($this->isPaginated() && $this->getPerPage() !== 'all') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $this->getPerPage() (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
2527
			return $this['paginator'];
2528
		}
2529
2530
		return NULL;
2531
	}
2532
2533
2534
	/********************************************************************************
2535
	 *                                     I18N                                     *
2536
	 ********************************************************************************/
2537
2538
2539
	/**
2540
	 * Set datagrid translator
2541
	 * @param Nette\Localization\ITranslator $translator
2542
	 * @return static
2543
	 */
2544
	public function setTranslator(Nette\Localization\ITranslator $translator)
2545
	{
2546
		$this->translator = $translator;
2547
2548
		return $this;
2549
	}
2550
2551
2552
	/**
2553
	 * Get translator for datagrid
2554
	 * @return Nette\Localization\ITranslator
2555
	 */
2556
	public function getTranslator()
2557
	{
2558
		if (!$this->translator) {
2559
			$this->translator = new Localization\SimpleTranslator;
2560
		}
2561
2562
		return $this->translator;
2563
	}
2564
2565
2566
	/********************************************************************************
2567
	 *                                 COLUMNS ORDER                                *
2568
	 ********************************************************************************/
2569
2570
2571
	/**
2572
	 * Set order of datagrid columns
2573
	 * @param array $order
2574
	 * @return static
2575
	 */
2576
	public function setColumnsOrder($order)
2577
	{
2578
		$new_order = [];
2579
2580
		foreach ($order as $key) {
2581
			if (isset($this->columns[$key])) {
2582
				$new_order[$key] = $this->columns[$key];
2583
			}
2584
		}
2585
2586
		if (sizeof($new_order) === sizeof($this->columns)) {
2587
			$this->columns = $new_order;
2588
		} else {
2589
			throw new DataGridException('When changing columns order, you have to specify all columns');
2590
		}
2591
2592
		return $this;
2593
	}
2594
2595
2596
	/**
2597
	 * Columns order may be different for export and normal grid
2598
	 * @param array $order
2599
	 */
2600
	public function setColumnsExportOrder($order)
2601
	{
2602
		$this->columns_export_order = (array) $order;
2603
	}
2604
2605
2606
	/********************************************************************************
2607
	 *                                SESSION & URL                                 *
2608
	 ********************************************************************************/
2609
2610
2611
	/**
2612
	 * Find some unique session key name
2613
	 * @return string
2614
	 */
2615
	public function getSessionSectionName()
2616
	{
2617
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2618
	}
2619
2620
2621
	/**
2622
	 * Should datagrid remember its filters/pagination/etc using session?
2623
	 * @param bool $remember
2624
	 * @return static
2625
	 */
2626
	public function setRememberState($remember = TRUE)
2627
	{
2628
		$this->remember_state = (bool) $remember;
2629
2630
		return $this;
2631
	}
2632
2633
2634
	/**
2635
	 * Should datagrid refresh url using history API?
2636
	 * @param bool $refresh
2637
	 * @return static
2638
	 */
2639
	public function setRefreshUrl($refresh = TRUE)
2640
	{
2641
		$this->refresh_url = (bool) $refresh;
2642
2643
2644
		return $this;
2645
	}
2646
2647
2648
	/**
2649
	 * Get session data if functionality is enabled
2650
	 * @param  string $key
2651
	 * @return mixed
2652
	 */
2653
	public function getSessionData($key = NULL, $default_value = NULL)
2654
	{
2655
		if (!$this->remember_state) {
2656
			return $key ? $default_value : [];
2657
		}
2658
2659
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2660
	}
2661
2662
2663
	/**
2664
	 * Save session data - just if it is enabled
2665
	 * @param  string $key
2666
	 * @param  mixed  $value
2667
	 * @return void
2668
	 */
2669
	public function saveSessionData($key, $value)
2670
	{
2671
		if ($this->remember_state) {
2672
			$this->grid_session->{$key} = $value;
2673
		}
2674
	}
2675
2676
2677
	/**
2678
	 * Delete session data
2679
	 * @return void
2680
	 */
2681
	public function deleteSessionData($key)
2682
	{
2683
		unset($this->grid_session->{$key});
2684
	}
2685
2686
2687
	/********************************************************************************
2688
	 *                                  ITEM DETAIL                                 *
2689
	 ********************************************************************************/
2690
2691
2692
	/**
2693
	 * Get items detail parameters
2694
	 * @return array
2695
	 */
2696
	public function getItemsDetail()
2697
	{
2698
		return $this->items_detail;
2699
	}
2700
2701
2702
	/**
2703
	 * Items can have thair detail - toggled
2704
	 * @param mixed $detail callable|string|bool
2705
	 * @param bool|NULL $primary_where_column
2706
	 * @return Column\ItemDetail
2707
	 */
2708
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2709
	{
2710
		if ($this->isSortable()) {
2711
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2712
		}
2713
2714
		$this->items_detail = new Column\ItemDetail(
2715
			$this,
2716
			$primary_where_column ?: $this->primary_key
0 ignored issues
show
Bug introduced by
It seems like $primary_where_column ?: $this->primary_key can also be of type boolean; however, Ublaboo\DataGrid\Column\ItemDetail::__construct() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
2717
		);
2718
2719
		if (is_string($detail)) {
2720
			/**
2721
			 * Item detail will be in separate template
2722
			 */
2723
			$this->items_detail->setType('template');
2724
			$this->items_detail->setTemplate($detail);
2725
2726
		} else if (is_callable($detail)) {
2727
			/**
2728
			 * Item detail will be rendered via custom callback renderer
2729
			 */
2730
			$this->items_detail->setType('renderer');
2731
			$this->items_detail->setRenderer($detail);
2732
2733
		} else if (TRUE === $detail) {
2734
			/**
2735
			 * Item detail will be rendered probably via block #detail
2736
			 */
2737
			$this->items_detail->setType('block');
2738
2739
		} else {
2740
			throw new DataGridException(
2741
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2742
			);
2743
		}
2744
2745
		return $this->items_detail;
2746
	}
2747
2748
2749
	/**
2750
	 * @param callable $callable_set_container 
2751
	 * @return static
2752
	 */
2753
	public function setItemsDetailForm(callable $callable_set_container)
2754
	{
2755
		if ($this->items_detail instanceof Column\ItemDetail) {
2756
			$this->items_detail->setForm(
2757
				new Utils\ItemDetailForm($callable_set_container)
2758
			);
2759
2760
			return $this;
2761
		}
2762
2763
		throw new DataGridException('Please set the ItemDetail first.');
2764
	}
2765
2766
2767
	/**
2768
	 * @return Nette\Forms\Container|NULL
2769
	 */
2770
	public function getItemDetailForm()
2771
	{
2772
		if ($this->items_detail instanceof Column\ItemDetail) {
2773
			return $this->items_detail->getForm();
2774
		}
2775
2776
		return NULL;
2777
	}
2778
2779
2780
	/********************************************************************************
2781
	 *                                ROW PRIVILEGES                                *
2782
	 ********************************************************************************/
2783
2784
2785
	/**
2786
	 * @param  callable $condition
2787
	 * @return void
2788
	 */
2789
	public function allowRowsGroupAction(callable $condition)
2790
	{
2791
		$this->row_conditions['group_action'] = $condition;
2792
	}
2793
2794
2795
	/**
2796
	 * @param  callable $condition
2797
	 * @return void
2798
	 */
2799
	public function allowRowsInlineEdit(callable $condition)
2800
	{
2801
		$this->row_conditions['inline_edit'] = $condition;
2802
	}
2803
2804
2805
	/**
2806
	 * @param  string   $key
2807
	 * @param  callable $condition
2808
	 * @return void
2809
	 */
2810
	public function allowRowsAction($key, callable $condition)
2811
	{
2812
		$this->row_conditions['action'][$key] = $condition;
2813
	}
2814
2815
2816
	/**
2817
	 * @param  string      $name
2818
	 * @param  string|null $key
2819
	 * @return bool|callable
2820
	 */
2821
	public function getRowCondition($name, $key = NULL)
2822
	{
2823
		if (!isset($this->row_conditions[$name])) {
2824
			return FALSE;
2825
		}
2826
2827
		$condition = $this->row_conditions[$name];
2828
2829
		if (!$key) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $key of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2830
			return $condition;
2831
		}
2832
2833
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2834
	}
2835
2836
2837
	/********************************************************************************
2838
	 *                               COLUMN CALLBACK                                *
2839
	 ********************************************************************************/
2840
2841
2842
	/**
2843
	 * @param  string   $key
2844
	 * @param  callable $callback
2845
	 * @return void
2846
	 */
2847
	public function addColumnCallback($key, callable $callback)
2848
	{
2849
		$this->column_callbacks[$key] = $callback;
2850
	}
2851
2852
2853
	/**
2854
	 * @param  string $key
2855
	 * @return callable|null
2856
	 */
2857
	public function getColumnCallback($key)
2858
	{
2859
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2860
	}
2861
2862
2863
	/********************************************************************************
2864
	 *                                 INLINE EDIT                                  *
2865
	 ********************************************************************************/
2866
2867
2868
	/**
2869
	 * @return InlineEdit
2870
	 */
2871
	public function addInlineEdit($primary_where_column = NULL)
2872
	{
2873
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2874
2875
		return $this->inlineEdit;
2876
	}
2877
2878
2879
	/**
2880
	 * @return InlineEdit|null
2881
	 */
2882
	public function getInlineEdit()
2883
	{
2884
		return $this->inlineEdit;
2885
	}
2886
2887
2888
	/**
2889
	 * @param  mixed $id
2890
	 * @return void
2891
	 */
2892
	public function handleInlineEdit($id)
2893
	{
2894
		if ($this->inlineEdit) {
2895
			$this->inlineEdit->setItemId($id);
2896
2897
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2898
2899
			$this['filter']['inline_edit']->addHidden('_id', $id);
2900
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2901
2902
			if ($this->getPresenter()->isAjax()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method isAjax() does only exist in the following implementations of said interface: Nette\Application\UI\Presenter.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2903
				$this->getPresenter()->payload->_datagrid_inline_editing = TRUE;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2904
			}
2905
2906
			$this->redrawItem($id, $primary_where_column);
2907
		}
2908
	}
2909
2910
2911
	/********************************************************************************
2912
	 *                                  INLINE ADD                                  *
2913
	 ********************************************************************************/
2914
2915
2916
	/**
2917
	 * @return InlineEdit
2918
	 */
2919
	public function addInlineAdd()
2920
	{
2921
		$this->inlineAdd = new InlineEdit($this);
2922
2923
		$this->inlineAdd
2924
			->setTitle('ublaboo_datagrid.add')
2925
			->setIcon('plus')
2926
			->setClass('btn btn-xs btn-default');
2927
2928
		return $this->inlineAdd;
2929
	}
2930
2931
2932
	/**
2933
	 * @return InlineEdit|null
2934
	 */
2935
	public function getInlineAdd()
2936
	{
2937
		return $this->inlineAdd;
2938
	}
2939
2940
2941
	/********************************************************************************
2942
	 *                               HIDEABLE COLUMNS                               *
2943
	 ********************************************************************************/
2944
2945
2946
	/**
2947
	 * Can datagrid hide colums?
2948
	 * @return boolean
2949
	 */
2950
	public function canHideColumns()
2951
	{
2952
		return (bool) $this->can_hide_columns;
2953
	}
2954
2955
2956
	/**
2957
	 * Order Grid to set columns hideable.
2958
	 * @return static
2959
	 */
2960
	public function setColumnsHideable()
2961
	{
2962
		$this->can_hide_columns = TRUE;
2963
2964
		return $this;
2965
	}
2966
2967
2968
	/********************************************************************************
2969
	 *                                COLUMNS SUMMARY                               *
2970
	 ********************************************************************************/
2971
2972
2973
	/**
2974
	 * Will datagrid show summary in the end?
2975
	 * @return bool
2976
	 */
2977
	public function hasColumnsSummary()
2978
	{
2979
		return $this->columnsSummary instanceof ColumnsSummary;
2980
	}
2981
2982
2983
	/**
2984
	 * Set columns to be summarized in the end.
2985
	 * @param array    $columns
2986
	 * @param callable $rowCallback
2987
	 * @return \Ublaboo\DataGrid\ColumnsSummary
2988
	 */
2989
	public function setColumnsSummary(array $columns, $rowCallback = NULL)
2990
	{
2991
		if (!empty($rowCallback)) {
2992
			if (!is_callable($rowCallback)) {
2993
				throw new \InvalidArgumentException('Row summary callback must be callable');
2994
			}
2995
		}
2996
2997
		$this->columnsSummary = new ColumnsSummary($this, $columns, $rowCallback);
2998
2999
		return $this->columnsSummary;
3000
	}
3001
3002
3003
	/**
3004
	 * @return ColumnsSummary|NULL
3005
	 */
3006
	public function getColumnsSummary()
3007
	{
3008
		return $this->columnsSummary;
3009
	}
3010
3011
3012
	/********************************************************************************
3013
	 *                                   INTERNAL                                   *
3014
	 ********************************************************************************/
3015
3016
3017
	/**
3018
	 * Tell grid filters to by submitted automatically
3019
	 * @param bool $auto
3020
	 */
3021
	public function setAutoSubmit($auto = TRUE)
3022
	{
3023
		$this->auto_submit = (bool) $auto;
3024
3025
		return $this;
3026
	}
3027
3028
3029
	/**
3030
	 * @return bool
3031
	 */
3032
	public function hasAutoSubmit()
3033
	{
3034
		return $this->auto_submit;
3035
	}
3036
3037
3038
	/**
3039
	 * Submit button when no auto-submitting is used
3040
	 * @return Filter\SubmitButton
3041
	 */
3042
	public function getFilterSubmitButton()
3043
	{
3044
		if ($this->hasAutoSubmit()) {
3045
			throw new DataGridException(
3046
				'DataGrid has auto-submit. Turn it off before setting filter submit button.'
3047
			);
3048
		}
3049
3050
		if ($this->filter_submit_button === NULL) {
3051
			$this->filter_submit_button = new Filter\SubmitButton($this);
3052
		}
3053
3054
		return $this->filter_submit_button;
3055
	}
3056
3057
3058
	/********************************************************************************
3059
	 *                                   INTERNAL                                   *
3060
	 ********************************************************************************/
3061
3062
3063
	/**
3064
	 * Get count of columns
3065
	 * @return int
3066
	 */
3067
	public function getColumnsCount()
3068
	{
3069
		$count = sizeof($this->getColumns());
3070
3071
		if (!empty($this->actions)
3072
			|| $this->isSortable()
3073
			|| $this->getItemsDetail()
3074
			|| $this->getInlineEdit()
3075
			|| $this->getInlineAdd()) {
3076
			$count++;
3077
		}
3078
3079
		if ($this->hasGroupActions()) {
3080
			$count++;
3081
		}
3082
3083
		return $count;
3084
	}
3085
3086
3087
	/**
3088
	 * Get primary key of datagrid data source
3089
	 * @return string
3090
	 */
3091
	public function getPrimaryKey()
3092
	{
3093
		return $this->primary_key;
3094
	}
3095
3096
3097
	/**
3098
	 * Get set of set columns
3099
	 * @return Column\IColumn[]
3100
	 */
3101
	public function getColumns()
3102
	{
3103
		$return = $this->columns;
3104
3105
		try {
3106
			$this->getParent();
3107
3108
			if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
3109
				$columns_to_hide = [];
3110
3111
				foreach ($this->columns as $key => $column) {
3112
					if ($column->getDefaultHide()) {
3113
						$columns_to_hide[] = $key;
3114
					}
3115
				}
3116
3117
				if (!empty($columns_to_hide)) {
3118
					$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
3119
					$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
3120
				}
3121
			}
3122
3123
			$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
3124
3125
			foreach ($hidden_columns as $column) {
3126
				if (!empty($this->columns[$column])) {
3127
					$this->columns_visibility[$column] = [
3128
						'visible' => FALSE
3129
					];
3130
3131
					unset($return[$column]);
3132
				}
3133
			}
3134
3135
		} catch (DataGridHasToBeAttachedToPresenterComponentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
3136
		}
3137
3138
		return $return;
3139
	}
3140
3141
3142
	public function getColumnsVisibility()
3143
	{
3144
		$return = $this->columns_visibility;
3145
3146
		foreach ($this->columns_visibility as $key => $column) {
3147
			$return[$key]['column'] = $this->columns[$key];
3148
		}
3149
3150
		return $return;
3151
	}
3152
3153
3154
	/**
3155
	 * @return PresenterComponent
3156
	 */
3157
	public function getParent()
3158
	{
3159
		$parent = parent::getParent();
3160
3161
		if (!($parent instanceof PresenterComponent)) {
0 ignored issues
show
Bug introduced by
The class Nette\Application\UI\PresenterComponent does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
3162
			throw new DataGridHasToBeAttachedToPresenterComponentException(
3163
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
3164
			);
3165
		}
3166
3167
		return $parent;
3168
	}
3169
3170
3171
	/**
3172
	 * @return strign
3173
	 */
3174
	public function getSortableParentPath()
3175
	{
3176
		return $this->getParent()->lookupPath(Nette\Application\UI\Control::class, FALSE);
3177
	}
3178
3179
3180
	/**
3181
	 * Some of datagrid columns is hidden by default
3182
	 * @param bool $default_hide
3183
	 */
3184
	public function setSomeColumnDefaultHide($default_hide)
3185
	{
3186
		$this->some_column_default_hide = $default_hide;
3187
	}
3188
3189
3190
	/**
3191
	 * Are some of columns hidden bydefault?
3192
	 */
3193
	public function hasSomeColumnDefaultHide()
3194
	{
3195
		return $this->some_column_default_hide;
3196
	}
3197
3198
3199
	/**
3200
	 * Simply refresh url
3201
	 * @return void
3202
	 */
3203
	public function handleRefreshState()
3204
	{
3205
		$this->findSessionValues();
3206
		$this->findDefaultFilter();
3207
		$this->findDefaultSort();
3208
		$this->findDefaultPerPage();
3209
3210
		$this->getPresenter()->payload->_datagrid_url = $this->refresh_url;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
3211
		$this->redrawControl('non-existing-snippet');
3212
	}
3213
3214
}
3215