Completed
Push — master ( 579809...c84864 )
by Pavel
02:48
created

DataGrid::handleRefreshState()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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