Completed
Push — master ( d2eb04...218fde )
by Pavel
02:52
created

DataGrid::setDefaultPerPage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid;
10
11
use Nette;
12
use Nette\Application\UI\Link;
13
use Nette\Application\UI\PresenterComponent;
14
use Ublaboo\DataGrid\Utils\ArraysHelper;
15
use Nette\Application\UI\Form;
16
use Ublaboo\DataGrid\Exception\DataGridException;
17
use Ublaboo\DataGrid\Exception\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 callable[]
53
	 */
54
	public $onFiltersAssabled;
55
56
	/**
57
	 * @var string
58
	 */
59
	public static $icon_prefix = 'fa fa-';
60
61
	/**
62
	 * Default form method
63
	 * @var string
64
	 */
65
	public static $form_method = 'post';
66
67
	/**
68
	 * When set to TRUE, datagrid throws an exception
69
	 * 	when tring to get related entity within join and entity does not exist
70
	 * @var bool
71
	 */
72
	public $strict_entity_property = FALSE;
73
74
	/**
75
	 * When set to TRUE, datagrid throws an exception
76
	 * 	when tring to set filter value, that does not exist (select, multiselect, etc)
77
	 * @var bool
78
	 */
79
	public $strict_session_filter_values = TRUE;
80
81
	/**
82
	 * @var int
83
	 * @persistent
84
	 */
85
	public $page = 1;
86
87
	/**
88
	 * @var int|string
89
	 * @persistent
90
	 */
91
	public $per_page;
92
93
	/**
94
	 * @var array
95
	 * @persistent
96
	 */
97
	public $sort = [];
98
99
	/**
100
	 * @var array
101
	 */
102
	public $default_sort = [];
103
104
	/**
105
	 * @var array
106
	 */
107
	public $default_filter = [];
108
109
	/**
110
	 * @var bool
111
	 */
112
	public $default_filter_use_on_reset = TRUE;
113
114
	/**
115
	 * @var array
116
	 * @persistent
117
	 */
118
	public $filter = [];
119
120
	/**
121
	 * @var callable|null
122
	 */
123
	protected $sort_callback = NULL;
124
125
	/**
126
	 * @var bool
127
	 */
128
	protected $use_happy_components = TRUE;
129
130
	/**
131
	 * @var callable
132
	 */
133
	protected $rowCallback;
134
135
	/**
136
	 * @var array
137
	 */
138
	protected $items_per_page_list;
139
140
	/**
141
	 * @var int
142
	 */
143
	protected $default_per_page;
144
145
	/**
146
	 * @var string
147
	 */
148
	protected $template_file;
149
150
	/**
151
	 * @var Column\IColumn[]
152
	 */
153
	protected $columns = [];
154
155
	/**
156
	 * @var Column\Action[]
157
	 */
158
	protected $actions = [];
159
160
	/**
161
	 * @var GroupAction\GroupActionCollection
162
	 */
163
	protected $group_action_collection;
164
165
	/**
166
	 * @var Filter\Filter[]
167
	 */
168
	protected $filters = [];
169
170
	/**
171
	 * @var Export\Export[]
172
	 */
173
	protected $exports = [];
174
175
	/**
176
	 * @var ToolbarButton[]
177
	 */
178
	protected $toolbar_buttons = [];
179
180
	/**
181
	 * @var DataModel
182
	 */
183
	protected $dataModel;
184
185
	/**
186
	 * @var DataFilter
187
	 */
188
	protected $dataFilter;
189
190
	/**
191
	 * @var string
192
	 */
193
	protected $primary_key = 'id';
194
195
	/**
196
	 * @var bool
197
	 */
198
	protected $do_paginate = TRUE;
199
200
	/**
201
	 * @var bool
202
	 */
203
	protected $csv_export = TRUE;
204
205
	/**
206
	 * @var bool
207
	 */
208
	protected $csv_export_filtered = TRUE;
209
210
	/**
211
	 * @var bool
212
	 */
213
	protected $sortable = FALSE;
214
215
	/**
216
	 * @var string
217
	 */
218
	protected $sortable_handler = 'sort!';
219
220
	/**
221
	 * @var string
222
	 */
223
	protected $original_template;
224
225
	/**
226
	 * @var array
227
	 */
228
	protected $redraw_item;
229
230
	/**
231
	 * @var mixed
232
	 */
233
	protected $translator;
234
235
	/**
236
	 * @var bool
237
	 */
238
	protected $force_filter_active;
239
240
	/**
241
	 * @var callable
242
	 */
243
	protected $tree_view_children_callback;
244
245
	/**
246
	 * @var callable
247
	 */
248
	protected $tree_view_has_children_callback;
249
250
	/**
251
	 * @var string
252
	 */
253
	protected $tree_view_has_children_column;
254
255
	/**
256
	 * @var bool
257
	 */
258
	protected $outer_filter_rendering = FALSE;
259
260
	/**
261
	 * @var array
262
	 */
263
	protected $columns_export_order = [];
264
265
	/**
266
	 * @var bool
267
	 */
268
	protected $remember_state = TRUE;
269
270
	/**
271
	 * @var bool
272
	 */
273
	protected $refresh_url = TRUE;
274
275
	/**
276
	 * @var Nette\Http\SessionSection
277
	 */
278
	protected $grid_session;
279
280
	/**
281
	 * @var Column\ItemDetail
282
	 */
283
	protected $items_detail;
284
285
	/**
286
	 * @var array
287
	 */
288
	protected $row_conditions = [
289
		'group_action' => FALSE,
290
		'action' => []
291
	];
292
293
	/**
294
	 * @var array
295
	 */
296
	protected $column_callbacks = [];
297
298
	/**
299
	 * @var bool
300
	 */
301
	protected $can_hide_columns = FALSE;
302
303
	/**
304
	 * @var array
305
	 */
306
	protected $columns_visibility = [];
307
308
	/**
309
	 * @var InlineEdit
310
	 */
311
	protected $inlineEdit;
312
313
	/**
314
	 * @var InlineEdit
315
	 */
316
	protected $inlineAdd;
317
318
	/**
319
	 * @var bool
320
	 */
321
	protected $snippets_set = FALSE;
322
323
	/**
324
	 * @var bool
325
	 */
326
	protected $some_column_default_hide = FALSE;
327
328
	/**
329
	 * @var ColumnsSummary
330
	 */
331
	protected $columnsSummary;
332
333
	/**
334
	 * @var bool
335
	 */
336
	protected $auto_submit = TRUE;
337
338
	/**
339
	 * @var Filter\SubmitButton|NULL
340
	 */
341
	protected $filter_submit_button = NULL;
342
343
	/**
344
	 * @var bool
345
	 */
346
	protected $has_column_reset = TRUE;
347
348
349
	/**
350
	 * @param Nette\ComponentModel\IContainer|NULL $parent
351
	 * @param string                               $name
352
	 */
353
	public function __construct(Nette\ComponentModel\IContainer $parent = NULL, $name = NULL)
354
	{
355
		parent::__construct($parent, $name);
356
357
		$this->monitor('Nette\Application\UI\Presenter');
358
359
		/**
360
		 * Try to find previous filters, pagination, per_page and other values in session
361
		 */
362
		$this->onRender[] = [$this, 'findSessionValues'];
363
		$this->onExport[] = [$this, 'findSessionValues'];
364
365
		/**
366
		 * Find default filter values
367
		 */
368
		$this->onRender[] = [$this, 'findDefaultFilter'];
369
		$this->onExport[] = [$this, 'findDefaultFilter'];
370
371
		/**
372
		 * Find default sort
373
		 */
374
		$this->onRender[] = [$this, 'findDefaultSort'];
375
		$this->onExport[] = [$this, 'findDefaultSort'];
376
377
		/**
378
		 * Find default items per page
379
		 */
380
		$this->onRender[] = [$this, 'findDefaultPerPage'];
381
382
		/**
383
		 * Notify about that json js extension
384
		 */
385
		$this->onFiltersAssabled[] = [$this, 'sendNonEmptyFiltersInPayload'];
386
	}
387
388
389
	/**
390
	 * {inheritDoc}
391
	 * @return void
392
	 */
393
	public function attached($presenter)
394
	{
395
		parent::attached($presenter);
396
397
		if ($presenter instanceof Nette\Application\UI\Presenter) {
398
			/**
399
			 * Get session
400
			 */
401
			if ($this->remember_state) {
402
				$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...
403
			}
404
		}
405
	}
406
407
408
	/********************************************************************************
409
	 *                                  RENDERING                                   *
410
	 ********************************************************************************/
411
412
413
	/**
414
	 * Render template
415
	 * @return void
416
	 */
417
	public function render()
418
	{
419
		/**
420
		 * Check whether datagrid has set some columns, initiated data source, etc
421
		 */
422
		if (!($this->dataModel instanceof DataModel)) {
423
			throw new DataGridException('You have to set a data source first.');
424
		}
425
426
		if (empty($this->columns)) {
427
			throw new DataGridException('You have to add at least one column.');
428
		}
429
430
		$this->getTemplate()->setTranslator($this->getTranslator());
431
432
		/**
433
		 * Invoke possible events
434
		 */
435
		$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...
436
437
		/**
438
		 * Prepare data for rendering (datagrid may render just one item)
439
		 */
440
		$rows = [];
441
442
		if (!empty($this->redraw_item)) {
443
			$items = $this->dataModel->filterRow($this->redraw_item);
444
		} else {
445
			$items = Nette\Utils\Callback::invokeArgs(
446
				[$this->dataModel, 'filterData'],
447
				[
448
					$this->getPaginator(),
449
					$this->createSorting($this->sort, $this->sort_callback),
450
					$this->assableFilters()
451
				]
452
			);
453
		}
454
455
		$callback = $this->rowCallback ?: NULL;
456
		$hasGroupActionOnRows = FALSE;
457
458
		foreach ($items as $item) {
459
			$rows[] = $row = new Row($this, $item, $this->getPrimaryKey());
460
461
			if (!$hasGroupActionOnRows && $row->hasGroupAction()){
462
				$hasGroupActionOnRows = TRUE;
463
			}
464
			
465
			if ($callback) {
466
				$callback($item, $row->getControl());
467
			}
468
469
			/**
470
			 * Walkaround for item snippet - snippet is the <tr> element and its class has to be also updated
471
			 */
472
			if (!empty($this->redraw_item)) {
473
				$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...
474
				$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...
475
			}
476
		}
477
478
		if ($hasGroupActionOnRows){
479
			$hasGroupActionOnRows = $this->hasGroupActions();
480
		}
481
482
		if ($this->isTreeView()) {
483
			$this->getTemplate()->add('tree_view_has_children_column', $this->tree_view_has_children_column);
484
		}
485
486
		$this->getTemplate()->add('rows', $rows);
487
488
		$this->getTemplate()->add('columns', $this->getColumns());
489
		$this->getTemplate()->add('actions', $this->actions);
490
		$this->getTemplate()->add('exports', $this->exports);
491
		$this->getTemplate()->add('filters', $this->filters);
492
		$this->getTemplate()->add('toolbar_buttons', $this->toolbar_buttons);
493
494
		$this->getTemplate()->add('filter_active', $this->isFilterActive());
495
		$this->getTemplate()->add('original_template', $this->getOriginalTemplateFile());
496
		//$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...
497
		$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...
498
		$this->getTemplate()->add('items_detail', $this->items_detail);
499
		$this->getTemplate()->add('columns_visibility', $this->getColumnsVisibility());
500
		$this->getTemplate()->add('columnsSummary', $this->columnsSummary);
501
502
		$this->getTemplate()->add('inlineEdit', $this->inlineEdit);
503
		$this->getTemplate()->add('inlineAdd', $this->inlineAdd);
504
505
		$this->getTemplate()->add('hasGroupActionOnRows', $hasGroupActionOnRows);
506
507
		/**
508
		 * Walkaround for Latte (does not know $form in snippet in {form} etc)
509
		 */
510
		$this->getTemplate()->add('filter', $this['filter']);
511
512
		/**
513
		 * Set template file and render it
514
		 */
515
		$this->getTemplate()->setFile($this->getTemplateFile());
516
		$this->getTemplate()->render();
517
	}
518
519
520
	/********************************************************************************
521
	 *                                 ROW CALLBACK                                 *
522
	 ********************************************************************************/
523
524
525
	/**
526
	 * Each row can be modified with user callback
527
	 * @param  callable  $callback
528
	 * @return static
529
	 */
530
	public function setRowCallback(callable $callback)
531
	{
532
		$this->rowCallback = $callback;
533
534
		return $this;
535
	}
536
537
538
	/********************************************************************************
539
	 *                                 DATA SOURCE                                  *
540
	 ********************************************************************************/
541
542
543
	/**
544
	 * By default ID, you can change that
545
	 * @param string $primary_key
546
	 * @return static
547
	 */
548
	public function setPrimaryKey($primary_key)
549
	{
550
		if ($this->dataModel instanceof DataModel) {
551
			throw new DataGridException('Please set datagrid primary key before setting datasource.');
552
		}
553
554
		$this->primary_key = $primary_key;
555
556
		return $this;
557
	}
558
559
560
	/**
561
	 * Set Grid data source
562
	 * @param DataSource\IDataSource|array|\DibiFluent|Nette\Database\Table\Selection|\Doctrine\ORM\QueryBuilder $source
563
	 * @return static
564
	 */
565
	public function setDataSource($source)
566
	{
567
		$this->dataModel = new DataModel($source, $this->primary_key);
568
569
		return $this;
570
	}
571
572
573
	/**
574
	 * @return DataSource\IDataSource|NULL
575
	 */
576
	public function getDataSource()
577
	{
578
		if (!$this->dataModel) {
579
			return NULL;
580
		}
581
582
		return $this->dataModel->getDataSource();
583
	}
584
585
586
	/********************************************************************************
587
	 *                                  TEMPLATING                                  *
588
	 ********************************************************************************/
589
590
591
	/**
592
	 * Set custom template file to render
593
	 * @param string $template_file
594
	 * @return static
595
	 */
596
	public function setTemplateFile($template_file)
597
	{
598
		$this->template_file = $template_file;
599
600
		return $this;
601
	}
602
603
604
	/**
605
	 * Get DataGrid template file
606
	 * @return string
607
	 * @return static
608
	 */
609
	public function getTemplateFile()
610
	{
611
		return $this->template_file ?: $this->getOriginalTemplateFile();
612
	}
613
614
615
	/**
616
	 * Get DataGrid original template file
617
	 * @return string
618
	 */
619
	public function getOriginalTemplateFile()
620
	{
621
		return __DIR__.'/templates/datagrid.latte';
622
	}
623
624
625
	/**
626
	 * Tell datagrid wheteher to use or not happy components
627
	 * @param  boolean|NULL $use If not given, return value of static::$use_happy_components
628
	 * @return void|bool
629
	 */
630
	public function useHappyComponents($use = NULL)
631
	{
632
		if (NULL === $use) {
633
			return $this->use_happy_components;
634
		}
635
636
		$this->use_happy_components = (bool) $use;
637
	}
638
639
640
	/********************************************************************************
641
	 *                                   SORTING                                    *
642
	 ********************************************************************************/
643
644
645
	/**
646
	 * Set default sorting
647
	 * @param array $sort
648
	 * @return static
649
	 */
650
	public function setDefaultSort($sort)
651
	{
652
		if (is_string($sort)) {
653
			$sort = [$sort => 'ASC'];
654
		} else {
655
			$sort = (array) $sort;
656
		}
657
658
		$this->default_sort = $sort;
659
660
		return $this;
661
	}
662
663
664
	/**
665
	 * User may set default sorting, apply it
666
	 * @return void
667
	 */
668
	public function findDefaultSort()
669
	{
670
		if (!empty($this->sort)) {
671
			return;
672
		}
673
674
		if (!empty($this->default_sort)) {
675
			$this->sort = $this->default_sort;
676
		}
677
678
		$this->saveSessionData('_grid_sort', $this->sort);
679
	}
680
681
682
	/**
683
	 * Set grido to be sortable
684
	 * @param bool $sortable
685
	 * @return static
686
	 */
687
	public function setSortable($sortable = TRUE)
688
	{
689
		if ($this->getItemsDetail()) {
690
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
691
		}
692
693
		$this->sortable = (bool) $sortable;
694
695
		return $this;
696
	}
697
698
699
	/**
700
	 * Set sortable handle
701
	 * @param string $handler
702
	 * @return static
703
	 */
704
	public function setSortableHandler($handler = 'sort!')
705
	{
706
		$this->sortable_handler = (string) $handler;
707
708
		return $this;
709
	}
710
711
712
	/**
713
	 * Tell whether DataGrid is sortable
714
	 * @return bool
715
	 */
716
	public function isSortable()
717
	{
718
		return $this->sortable;
719
	}
720
721
	/**
722
	 * Return sortable handle name
723
	 * @return string
724
	 */
725
	public function getSortableHandler()
726
	{
727
		return $this->sortable_handler;
728
	}
729
730
731
	/**
732
	 * @param  array         $sort
733
	 * @param  callable|NULL $sort_callback
734
	 * @return void
735
	 */
736
	protected function createSorting(array $sort, $sort_callback)
737
	{
738
		foreach ($sort as $key => $order) {
739
			$column = $this->getColumn($key);
740
			$sort = [$column->getSortingColumn() => $order];
741
		}
742
743
		// required for first request
744
		if (isset($column) && $sort_callback === NULL) {
745
			$sort_callback = $column->getSortableCallback();
746
		}
747
748
		return new Sorting($sort, $sort_callback);
749
	}
750
751
752
	/********************************************************************************
753
	 *                                  TREE VIEW                                   *
754
	 ********************************************************************************/
755
756
757
	/**
758
	 * Is tree view set?
759
	 * @return boolean
760
	 */
761
	public function isTreeView()
762
	{
763
		return (bool) $this->tree_view_children_callback;
764
	}
765
766
767
	/**
768
	 * Setting tree view
769
	 * @param callable $get_children_callback
770
	 * @param string|callable $tree_view_has_children_column
771
	 * @return static
772
	 */
773
	public function setTreeView($get_children_callback, $tree_view_has_children_column = 'has_children')
774
	{
775
		if (!is_callable($get_children_callback)) {
776
			throw new DataGridException(
777
				'Parameters to method DataGrid::setTreeView must be of type callable'
778
			);
779
		}
780
781
		if (is_callable($tree_view_has_children_column)) {
782
			$this->tree_view_has_children_callback = $tree_view_has_children_column;
783
			$tree_view_has_children_column = NULL;
784
		}
785
786
		$this->tree_view_children_callback = $get_children_callback;
787
		$this->tree_view_has_children_column = $tree_view_has_children_column;
788
789
		/**
790
		 * TUrn off pagination
791
		 */
792
		$this->setPagination(FALSE);
793
794
		/**
795
		 * Set tree view template file
796
		 */
797
		if (!$this->template_file) {
798
			$this->setTemplateFile(__DIR__.'/templates/datagrid_tree.latte');
799
		}
800
801
		return $this;
802
	}
803
804
805
	/**
806
	 * Is tree view children callback set?
807
	 * @return boolean
808
	 */
809
	public function hasTreeViewChildrenCallback()
810
	{
811
		return is_callable($this->tree_view_has_children_callback);
812
	}
813
814
815
	/**
816
	 * @param  mixed $item
817
	 * @return boolean
818
	 */
819
	public function treeViewChildrenCallback($item)
820
	{
821
		return call_user_func($this->tree_view_has_children_callback, $item);
822
	}
823
824
825
	/********************************************************************************
826
	 *                                    COLUMNS                                   *
827
	 ********************************************************************************/
828
829
830
	/**
831
	 * Add text column with no other formating
832
	 * @param  string      $key
833
	 * @param  string      $name
834
	 * @param  string|null $column
835
	 * @return Column\ColumnText
836
	 */
837
	public function addColumnText($key, $name, $column = NULL)
838
	{
839
		$this->addColumnCheck($key);
840
		$column = $column ?: $key;
841
842
		return $this->addColumn($key, new Column\ColumnText($this, $key, $column, $name));
843
	}
844
845
846
	/**
847
	 * Add column with link
848
	 * @param  string      $key
849
	 * @param  string      $name
850
	 * @param  string|null $column
851
	 * @return Column\ColumnLink
852
	 */
853
	public function addColumnLink($key, $name, $href = NULL, $column = NULL, array $params = NULL)
854
	{
855
		$this->addColumnCheck($key);
856
		$column = $column ?: $key;
857
		$href = $href ?: $key;
858
859
		if (NULL === $params) {
860
			$params = [$this->primary_key];
861
		}
862
863
		return $this->addColumn($key, new Column\ColumnLink($this, $key, $column, $name, $href, $params));
864
	}
865
866
867
	/**
868
	 * Add column with possible number formating
869
	 * @param  string      $key
870
	 * @param  string      $name
871
	 * @param  string|null $column
872
	 * @return Column\ColumnNumber
873
	 */
874
	public function addColumnNumber($key, $name, $column = NULL)
875
	{
876
		$this->addColumnCheck($key);
877
		$column = $column ?: $key;
878
879
		return $this->addColumn($key, new Column\ColumnNumber($this, $key, $column, $name));
880
	}
881
882
883
	/**
884
	 * Add column with date formating
885
	 * @param  string      $key
886
	 * @param  string      $name
887
	 * @param  string|null $column
888
	 * @return Column\ColumnDateTime
889
	 */
890
	public function addColumnDateTime($key, $name, $column = NULL)
891
	{
892
		$this->addColumnCheck($key);
893
		$column = $column ?: $key;
894
895
		return $this->addColumn($key, new Column\ColumnDateTime($this, $key, $column, $name));
896
	}
897
898
899
	/**
900
	 * Add column status
901
	 * @param  string      $key
902
	 * @param  string      $name
903
	 * @param  string|null $column
904
	 * @return Column\ColumnStatus
905
	 */
906
	public function addColumnStatus($key, $name, $column = NULL)
907
	{
908
		$this->addColumnCheck($key);
909
		$column = $column ?: $key;
910
911
		return $this->addColumn($key, new Column\ColumnStatus($this, $key, $column, $name));
912
	}
913
914
915
	/**
916
	 * @param string $key
917
	 * @param Column\Column $column
918
	 * @return Column\Column
919
	 */
920
	protected function addColumn($key, Column\Column $column)
921
	{
922
		$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...
923
924
		$this->columns_visibility[$key] = [
925
			'visible' => TRUE
926
		];
927
928
		return $this->columns[$key] = $column;
929
	}
930
931
932
	/**
933
	 * Return existing column
934
	 * @param  string $key
935
	 * @return Column\Column
936
	 * @throws DataGridException
937
	 */
938
	public function getColumn($key)
939
	{
940
		if (!isset($this->columns[$key])) {
941
			throw new DataGridException("There is no column at key [$key] defined.");
942
		}
943
944
		return $this->columns[$key];
945
	}
946
947
948
	/**
949
	 * Remove column
950
	 * @param string $key
951
	 * @return void
952
	 */
953
	public function removeColumn($key)
954
	{
955
		unset($this->columns_visibility[$key]);
956
		unset($this->columns[$key]);
957
	}
958
959
960
	/**
961
	 * Check whether given key already exists in $this->columns
962
	 * @param  string $key
963
	 * @throws DataGridException
964
	 */
965
	protected function addColumnCheck($key)
966
	{
967
		if (isset($this->columns[$key])) {
968
			throw new DataGridException("There is already column at key [$key] defined.");
969
		}
970
	}
971
972
973
	/********************************************************************************
974
	 *                                    ACTIONS                                   *
975
	 ********************************************************************************/
976
977
978
	/**
979
	 * Create action
980
	 * @param string     $key
981
	 * @param string     $name
982
	 * @param string     $href
983
	 * @param array|null $params
984
	 * @return Column\Action
985
	 */
986
	public function addAction($key, $name, $href = NULL, array $params = NULL)
987
	{
988
		$this->addActionCheck($key);
989
		$href = $href ?: $key;
990
991
		if (NULL === $params) {
992
			$params = [$this->primary_key];
993
		}
994
995
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
996
	}
997
998
999
	/**
1000
	 * Create action callback
1001
	 * @param string     $key
1002
	 * @param string     $name
1003
	 * @return Column\Action
1004
	 */
1005
	public function addActionCallback($key, $name, $callback = NULL)
1006
	{
1007
		$this->addActionCheck($key);
1008
		$params = ['__id' => $this->primary_key];
1009
1010
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
1011
1012
		if ($callback) {
1013
			if (!is_callable($callback)) {
1014
				throw new DataGridException('ActionCallback callback has to be callable.');
1015
			}
1016
1017
			$action->onClick[] = $callback;
1018
		}
1019
1020
		return $action;
1021
	}
1022
1023
1024
	/**
1025
	 * Get existing action
1026
	 * @param  string       $key
1027
	 * @return Column\Action
1028
	 * @throws DataGridException
1029
	 */
1030
	public function getAction($key)
1031
	{
1032
		if (!isset($this->actions[$key])) {
1033
			throw new DataGridException("There is no action at key [$key] defined.");
1034
		}
1035
1036
		return $this->actions[$key];
1037
	}
1038
1039
1040
	/**
1041
	 * Remove action
1042
	 * @param string $key
1043
	 * @return void
1044
	 */
1045
	public function removeAction($key)
1046
	{
1047
		unset($this->actions[$key]);
1048
	}
1049
1050
1051
	/**
1052
	 * Check whether given key already exists in $this->filters
1053
	 * @param  string $key
1054
	 * @throws DataGridException
1055
	 */
1056
	protected function addActionCheck($key)
1057
	{
1058
		if (isset($this->actions[$key])) {
1059
			throw new DataGridException("There is already action at key [$key] defined.");
1060
		}
1061
	}
1062
1063
1064
	/********************************************************************************
1065
	 *                                    FILTERS                                   *
1066
	 ********************************************************************************/
1067
1068
1069
	/**
1070
	 * Add filter fot text search
1071
	 * @param string       $key
1072
	 * @param string       $name
1073
	 * @param array|string $columns
1074
	 * @return Filter\FilterText
1075
	 * @throws DataGridException
1076
	 */
1077
	public function addFilterText($key, $name, $columns = NULL)
1078
	{
1079
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
1080
1081
		if (!is_array($columns)) {
1082
			throw new DataGridException("Filter Text can accept only array or string.");
1083
		}
1084
1085
		$this->addFilterCheck($key);
1086
1087
		return $this->filters[$key] = new Filter\FilterText($this, $key, $name, $columns);
1088
	}
1089
1090
1091
	/**
1092
	 * Add select box filter
1093
	 * @param string $key
1094
	 * @param string $name
1095
	 * @param array  $options
1096
	 * @param string $column
1097
	 * @return Filter\FilterSelect
1098
	 * @throws DataGridException
1099
	 */
1100 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...
1101
	{
1102
		$column = $column ?: $key;
1103
1104
		if (!is_string($column)) {
1105
			throw new DataGridException("Filter Select can only filter in one column.");
1106
		}
1107
1108
		$this->addFilterCheck($key);
1109
1110
		return $this->filters[$key] = new Filter\FilterSelect($this, $key, $name, $options, $column);
1111
	}
1112
1113
1114
	/**
1115
	 * Add multi select box filter
1116
	 * @param string $key
1117
	 * @param string $name
1118
	 * @param array  $options
1119
	 * @param string $column
1120
	 * @return Filter\FilterSelect
1121
	 * @throws DataGridException
1122
	 */
1123 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...
1124
	{
1125
		$column = $column ?: $key;
1126
1127
		if (!is_string($column)) {
1128
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1129
		}
1130
1131
		$this->addFilterCheck($key);
1132
1133
		return $this->filters[$key] = new Filter\FilterMultiSelect($this, $key, $name, $options, $column);
1134
	}
1135
1136
1137
	/**
1138
	 * Add datepicker filter
1139
	 * @param string $key
1140
	 * @param string $name
1141
	 * @param string $column
1142
	 * @return Filter\FilterDate
1143
	 * @throws DataGridException
1144
	 */
1145 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...
1146
	{
1147
		$column = $column ?: $key;
1148
1149
		if (!is_string($column)) {
1150
			throw new DataGridException("FilterDate can only filter in one column.");
1151
		}
1152
1153
		$this->addFilterCheck($key);
1154
1155
		return $this->filters[$key] = new Filter\FilterDate($this, $key, $name, $column);
1156
	}
1157
1158
1159
	/**
1160
	 * Add range filter (from - to)
1161
	 * @param string $key
1162
	 * @param string $name
1163
	 * @param string $column
1164
	 * @return Filter\FilterRange
1165
	 * @throws DataGridException
1166
	 */
1167 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...
1168
	{
1169
		$column = $column ?: $key;
1170
1171
		if (!is_string($column)) {
1172
			throw new DataGridException("FilterRange can only filter in one column.");
1173
		}
1174
1175
		$this->addFilterCheck($key);
1176
1177
		return $this->filters[$key] = new Filter\FilterRange($this, $key, $name, $column, $name_second);
1178
	}
1179
1180
1181
	/**
1182
	 * Add datepicker filter (from - to)
1183
	 * @param string $key
1184
	 * @param string $name
1185
	 * @param string $column
1186
	 * @return Filter\FilterDateRange
1187
	 * @throws DataGridException
1188
	 */
1189 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...
1190
	{
1191
		$column = $column ?: $key;
1192
1193
		if (!is_string($column)) {
1194
			throw new DataGridException("FilterDateRange can only filter in one column.");
1195
		}
1196
1197
		$this->addFilterCheck($key);
1198
1199
		return $this->filters[$key] = new Filter\FilterDateRange($this, $key, $name, $column, $name_second);
1200
	}
1201
1202
1203
	/**
1204
	 * Check whether given key already exists in $this->filters
1205
	 * @param  string $key
1206
	 * @throws DataGridException
1207
	 */
1208
	protected function addFilterCheck($key)
1209
	{
1210
		if (isset($this->filters[$key])) {
1211
			throw new DataGridException("There is already action at key [$key] defined.");
1212
		}
1213
	}
1214
1215
1216
	/**
1217
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1218
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1219
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1220
	 */
1221
	public function assableFilters()
1222
	{
1223
		foreach ($this->filter as $key => $value) {
1224
			if (!isset($this->filters[$key])) {
1225
				$this->deleteSesssionData($key);
1226
1227
				continue;
1228
			}
1229
1230
			if (is_array($value) || $value instanceof \Traversable) {
1231
				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...
1232
					$this->filters[$key]->setValue($value);
1233
				}
1234
			} else {
1235
				if ($value !== '' && $value !== NULL) {
1236
					$this->filters[$key]->setValue($value);
1237
				}
1238
			}
1239
		}
1240
1241
		foreach ($this->columns as $key => $column) {
1242
			if (isset($this->sort[$key])) {
1243
				$column->setSort($this->sort);
1244
			}
1245
		}
1246
1247
		/**
1248
		 * Invoke possible events
1249
		 */
1250
		$this->onFiltersAssabled($this->filters);
0 ignored issues
show
Documentation Bug introduced by
The method onFiltersAssabled does not exist on object<Ublaboo\DataGrid\DataGrid>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1251
1252
		return $this->filters;
1253
	}
1254
1255
1256
	/**
1257
	 * Remove filter
1258
	 * @param string $key
1259
	 * @return void
1260
	 */
1261
	public function removeFilter($key)
1262
	{
1263
		unset($this->filters[$key]);
1264
	}
1265
1266
1267
	/**
1268
	 * Get defined filter
1269
	 * @param  string $key
1270
	 * @return Filter\Filter
1271
	 */
1272
	public function getFilter($key)
1273
	{
1274
		if (!isset($this->filters[$key])) {
1275
			throw new DataGridException("Filter [{$key}] is not defined");
1276
		}
1277
1278
		return $this->filters[$key];
1279
	}
1280
1281
1282
	/**
1283
	 * @param bool $strict
1284
	 */
1285
	public function setStrictSessionFilterValues($strict = TRUE)
1286
	{
1287
		$this->strict_session_filter_values = (bool) $strict;
1288
	}
1289
1290
1291
	/********************************************************************************
1292
	 *                                  FILTERING                                   *
1293
	 ********************************************************************************/
1294
1295
1296
	/**
1297
	 * Is filter active?
1298
	 * @return boolean
1299
	 */
1300
	public function isFilterActive()
1301
	{
1302
		$is_filter = ArraysHelper::testTruthy($this->filter);
1303
1304
		return ($is_filter) || $this->force_filter_active;
1305
	}
1306
1307
1308
	/**
1309
	 * Tell that filter is active from whatever reasons
1310
	 * return static
1311
	 */
1312
	public function setFilterActive()
1313
	{
1314
		$this->force_filter_active = TRUE;
1315
1316
		return $this;
1317
	}
1318
1319
1320
	/**
1321
	 * Set filter values (force - overwrite user data)
1322
	 * @param array $filter
1323
	 * @return static
1324
	 */
1325
	public function setFilter(array $filter)
1326
	{
1327
		$this->filter = $filter;
1328
1329
		$this->saveSessionData('_grid_has_filtered', 1);
1330
1331
		return $this;
1332
	}
1333
1334
1335
	/**
1336
	 * If we want to sent some initial filter
1337
	 * @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...
1338
	 * @param bool  $use_on_reset
1339
	 * @return static
1340
	 */
1341
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1342
	{
1343
		foreach ($default_filter as $key => $value) {
1344
			$filter = $this->getFilter($key);
1345
1346
			if (!$filter) {
1347
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1348
			}
1349
1350
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1351
				throw new DataGridException(
1352
					"Default value of filter [$key] - MultiSelect has to be an array"
1353
				);
1354
			}
1355
1356
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1357
				if (!is_array($value)) {
1358
					throw new DataGridException(
1359
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1360
					);
1361
				}
1362
1363
				$temp = $value;
1364
				unset($temp['from'], $temp['to']);
1365
1366
				if (!empty($temp)) {
1367
					throw new DataGridException(
1368
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1369
					);
1370
				}
1371
			}
1372
		}
1373
1374
		$this->default_filter = $default_filter;
1375
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1376
1377
		return $this;
1378
	}
1379
1380
1381
	/**
1382
	 * User may set default filter, find it
1383
	 * @return void
1384
	 */
1385
	public function findDefaultFilter()
1386
	{
1387
		if (!empty($this->filter)) {
1388
			return;
1389
		}
1390
1391
		if ($this->getSessionData('_grid_has_filtered')) {
1392
			return;
1393
		}
1394
1395
		if (!empty($this->default_filter)) {
1396
			$this->filter = $this->default_filter;
1397
		}
1398
1399
		foreach ($this->filter as $key => $value) {
1400
			$this->saveSessionData($key, $value);
1401
		}
1402
	}
1403
1404
1405
	/**
1406
	 * FilterAndGroupAction form factory
1407
	 * @return Form
1408
	 */
1409
	public function createComponentFilter()
1410
	{
1411
		$form = new Form($this, 'filter');
1412
1413
		$form->setMethod(static::$form_method);
1414
1415
		$form->setTranslator($this->getTranslator());
1416
1417
		/**
1418
		 * InlineEdit part
1419
		 */
1420
		$inline_edit_container = $form->addContainer('inline_edit');
1421
1422 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...
1423
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1424
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1425
				->setValidationScope(FALSE);
1426
1427
			$this->inlineEdit->onControlAdd($inline_edit_container);
1428
			$this->inlineEdit->onControlAfterAdd($inline_edit_container);
1429
		}
1430
1431
		/**
1432
		 * InlineAdd part
1433
		 */
1434
		$inline_add_container = $form->addContainer('inline_add');
1435
1436 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...
1437
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1438
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1439
				->setValidationScope(FALSE)
1440
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1441
1442
			$this->inlineAdd->onControlAdd($inline_add_container);
1443
			$this->inlineAdd->onControlAfterAdd($inline_add_container);
1444
		}
1445
1446
		/**
1447
		 * ItemDetail form part
1448
		 */
1449
		$items_detail_form = $this->getItemDetailForm();
1450
1451
		if ($items_detail_form instanceof Nette\Forms\Container) {
1452
			$form['items_detail_form'] = $items_detail_form;
1453
		}
1454
1455
		/**
1456
		 * Filter part
1457
		 */
1458
		$filter_container = $form->addContainer('filter');
1459
1460
		foreach ($this->filters as $filter) {
1461
			$filter->addToFormContainer($filter_container);
1462
		}
1463
1464
		if (!$this->hasAutoSubmit()) {
1465
			$filter_container['submit'] = $this->getFilterSubmitButton();
1466
		}
1467
1468
		/**
1469
		 * Group action part
1470
		 */
1471
		$group_action_container = $form->addContainer('group_action');
1472
1473
		if ($this->hasGroupActions()) {
1474
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1475
		}
1476
1477
		if (!$form->isSubmitted()) {
1478
			$this->setFilterContainerDefaults($form['filter'], $this->filter);
1479
		}
1480
1481
		/**
1482
		 * Per page part
1483
		 */
1484
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1485
			->setTranslator(NULL);
1486
1487
		if (!$form->isSubmitted()) {
1488
			$form['per_page']->setValue($this->getPerPage());
1489
		}
1490
1491
		$form->addSubmit('per_page_submit', '');
1492
		
1493
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1494
	}
1495
1496
1497
	public function setFilterContainerDefaults(Nette\Forms\Container $container, array $values)
1498
	{
1499
		foreach ($container->getComponents() as $name => $control) {
1500
			if ($control instanceof Nette\Forms\IControl) {
1501 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...
1502
					try {
1503
						$control->setValue($values[$name]);
1504
					} catch (Nette\InvalidArgumentException $e) {
1505
						if ($this->strict_session_filter_values) {
1506
							throw $e;
1507
						}
1508
					}
1509
				}
1510
1511
			} elseif ($control instanceof Nette\Forms\Container) {
1512 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...
1513
					try {
1514
						$control->setValues($values[$name]);
1515
					} catch (Nette\InvalidArgumentException $e) {
1516
						if ($this->strict_session_filter_values) {
1517
							throw $e;
1518
						}
1519
					}
1520
				}
1521
			}
1522
		}
1523
	}
1524
1525
1526
	/**
1527
	 * Set $this->filter values after filter form submitted
1528
	 * @param  Form $form
1529
	 * @return void
1530
	 */
1531
	public function filterSucceeded(Form $form)
1532
	{
1533
		if ($this->snippets_set) {
1534
			return;
1535
		}
1536
1537
		$values = $form->getValues();
1538
1539
		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...
1540
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1541
				return;
1542
			}
1543
		}
1544
1545
		/**
1546
		 * Per page
1547
		 */
1548
		$this->saveSessionData('_grid_per_page', $values->per_page);
1549
		$this->per_page = $values->per_page;
1550
1551
		/**
1552
		 * Inline edit
1553
		 */
1554
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1555
			$edit = $form['inline_edit'];
1556
1557
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1558
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1559
				$primary_where_column = $form->getHttpData(
1560
					Form::DATA_LINE,
1561
					'inline_edit[_primary_where_column]'
1562
				);
1563
1564
				if ($edit['submit']->isSubmittedBy()) {
1565
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1566
					$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...
1567
					$this->getPresenter()->payload->_datagrid_name = $this->getName();
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

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

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1571
				}
1572
1573
				if ($edit['submit']->isSubmittedBy() && !empty($this->inlineEdit->onCustomRedraw)) {
1574
					$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...
1575
				} else {
1576
					$this->redrawItem($id, $primary_where_column);
1577
				}
1578
1579
				return;
1580
			}
1581
		}
1582
1583
		/**
1584
		 * Inline add
1585
		 */
1586
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1587
			$add = $form['inline_add'];
1588
1589
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1590
				if ($add['submit']->isSubmittedBy()) {
1591
					$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...
1592
1593
					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...
1594
						$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...
1595
					}
1596
				}
1597
1598
				return;
1599
			}
1600
		}
1601
1602
		/**
1603
		 * Filter itself
1604
		 */
1605
		$values = $values['filter'];
1606
1607
		foreach ($values as $key => $value) {
1608
			/**
1609
			 * Session stuff
1610
			 */
1611
			$this->saveSessionData($key, $value);
1612
1613
			/**
1614
			 * Other stuff
1615
			 */
1616
			$this->filter[$key] = $value;
1617
		}
1618
1619
		if (!empty($values)) {
1620
			$this->saveSessionData('_grid_has_filtered', 1);
1621
		}
1622
1623
		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...
1624
			$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...
1625
1626
			foreach ($this->columns as $key => $column) {
1627
				if ($column->isSortable()) {
1628
					$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...
1629
						'sort' => $column->getSortNext()
1630
					]);
1631
				}
1632
			}
1633
		}
1634
1635
		$this->reload();
1636
	}
1637
1638
1639
	/**
1640
	 * Should be datagrid filters rendered separately?
1641
	 * @param boolean $out
1642
	 * @return static
1643
	 */
1644
	public function setOuterFilterRendering($out = TRUE)
1645
	{
1646
		$this->outer_filter_rendering = (bool) $out;
1647
1648
		return $this;
1649
	}
1650
1651
1652
	/**
1653
	 * Are datagrid filters rendered separately?
1654
	 * @return boolean
1655
	 */
1656
	public function hasOuterFilterRendering()
1657
	{
1658
		return $this->outer_filter_rendering;
1659
	}
1660
1661
1662
	/**
1663
	 * Try to restore session stuff
1664
	 * @return void
1665
	 */
1666
	public function findSessionValues()
1667
	{
1668
		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...
1669
			return;
1670
		}
1671
1672
		if (!$this->remember_state) {
1673
			return;
1674
		}
1675
1676
		if ($page = $this->getSessionData('_grid_page')) {
1677
			$this->page = $page;
1678
		}
1679
1680
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1681
			$this->per_page = $per_page;
1682
		}
1683
1684
		if ($sort = $this->getSessionData('_grid_sort')) {
1685
			$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...
1686
		}
1687
1688
		foreach ($this->getSessionData() as $key => $value) {
1689
			$other_session_keys = [
1690
				'_grid_per_page',
1691
				'_grid_sort',
1692
				'_grid_page',
1693
				'_grid_has_filtered',
1694
				'_grid_hidden_columns',
1695
				'_grid_hidden_columns_manipulated'
1696
			];
1697
1698
			if (!in_array($key, $other_session_keys)) {
1699
				try {
1700
					$this->getFilter($key);
1701
1702
					$this->filter[$key] = $value;
1703
1704
				} catch (DataGridException $e) {
1705
					if ($this->strict_session_filter_values) {
1706
						throw new DataGridException("Session filter: Filter [$key] not found");
1707
					}
1708
				}
1709
			}
1710
		}
1711
1712
		/**
1713
		 * When column is sorted via custom callback, apply it
1714
		 */
1715
		if (empty($this->sort_callback) && !empty($this->sort)) {
1716
			foreach ($this->sort as $key => $order) {
1717
				try {
1718
					$column = $this->getColumn($key);
1719
1720
				} catch (DataGridException $e) {
1721
					$this->deleteSesssionData('_grid_sort');
1722
					$this->sort = [];
1723
1724
					return;
1725
				}
1726
1727
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1728
					$this->sort_callback = $column->getSortableCallback();
1729
				}
1730
			}
1731
		}
1732
	}
1733
1734
1735
	/********************************************************************************
1736
	 *                                    EXPORTS                                   *
1737
	 ********************************************************************************/
1738
1739
1740
	/**
1741
	 * Add export of type callback
1742
	 * @param string $text
1743
	 * @param callable $callback
1744
	 * @param boolean $filtered
1745
	 * @return Export\Export
1746
	 */
1747
	public function addExportCallback($text, $callback, $filtered = FALSE)
1748
	{
1749
		if (!is_callable($callback)) {
1750
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1751
		}
1752
1753
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1754
	}
1755
1756
1757
	/**
1758
	 * Add already implemented csv export
1759
	 * @param string $text
1760
	 * @param string $csv_file_name
1761
	 * @param string|null $output_encoding
1762
	 * @param string|null $delimiter
1763
	 * @param bool $include_bom
1764
	 * @return Export\Export
1765
	 */
1766 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...
1767
	{
1768
		return $this->addToExports(new Export\ExportCsv(
1769
			$this,
1770
			$text,
1771
			$csv_file_name,
1772
			FALSE,
1773
			$output_encoding,
1774
			$delimiter,
1775
			$include_bom
1776
		));
1777
	}
1778
1779
1780
	/**
1781
	 * Add already implemented csv export, but for filtered data
1782
	 * @param string $text
1783
	 * @param string $csv_file_name
1784
	 * @param string|null $output_encoding
1785
	 * @param string|null $delimiter
1786
	 * @param bool $include_bom
1787
	 * @return Export\Export
1788
	 */
1789 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...
1790
	{
1791
		return $this->addToExports(new Export\ExportCsv(
1792
			$this,
1793
			$text,
1794
			$csv_file_name,
1795
			TRUE,
1796
			$output_encoding,
1797
			$delimiter,
1798
			$include_bom
1799
		));
1800
	}
1801
1802
1803
	/**
1804
	 * Add export to array
1805
	 * @param Export\Export $export
1806
	 * @return Export\Export
1807
	 */
1808
	protected function addToExports(Export\Export $export)
1809
	{
1810
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1811
1812
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1813
1814
		return $this->exports[$id] = $export;
1815
	}
1816
1817
1818
	public function resetExportsLinks()
1819
	{
1820
		foreach ($this->exports as $id => $export) {
1821
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1822
		}
1823
	}
1824
1825
1826
	/********************************************************************************
1827
	 *                                TOOLBAR BUTTONS                               *
1828
	 ********************************************************************************/
1829
1830
1831
	/**
1832
	 * Add toolbar button
1833
	 * @param string $href
1834
	 * @param string $text
1835
	 * @param array  $params
1836
	 * @return ToolbarButton
1837
	 */
1838
	public function addToolbarButton($href, $text = '', $params = [])
1839
	{
1840
		$button = new ToolbarButton($this, $href, $text, $params);
1841
1842
		return $this->toolbar_buttons[] = $button;
1843
	}
1844
1845
1846
	/********************************************************************************
1847
	 *                                 GROUP ACTIONS                                *
1848
	 ********************************************************************************/
1849
1850
1851
	/**
1852
	 * Alias for add group select action
1853
	 * @param string $title
1854
	 * @param array  $options
1855
	 * @return GroupAction\GroupAction
1856
	 */
1857
	public function addGroupAction($title, $options = [])
1858
	{
1859
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1860
	}
1861
1862
1863
	/**
1864
	 * Add group action (select box)
1865
	 * @param string $title
1866
	 * @param array  $options
1867
	 * @return GroupAction\GroupAction
1868
	 */
1869
	public function addGroupSelectAction($title, $options = [])
1870
	{
1871
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1872
	}
1873
1874
1875
	/**
1876
	 * Add group action (text input)
1877
	 * @param string $title
1878
	 * @return GroupAction\GroupAction
1879
	 */
1880
	public function addGroupTextAction($title)
1881
	{
1882
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1883
	}
1884
1885
1886
	/**
1887
	 * Add group action (textarea)
1888
	 * @param string $title
1889
	 * @return GroupAction\GroupAction
1890
	 */
1891
	public function addGroupTextareaAction($title)
1892
	{
1893
		return $this->getGroupActionCollection()->addGroupTextareaAction($title);
1894
	}
1895
1896
1897
	/**
1898
	 * Get collection of all group actions
1899
	 * @return GroupAction\GroupActionCollection
1900
	 */
1901
	public function getGroupActionCollection()
1902
	{
1903
		if (!$this->group_action_collection) {
1904
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1905
		}
1906
1907
		return $this->group_action_collection;
1908
	}
1909
1910
1911
	/**
1912
	 * Has datagrid some group actions?
1913
	 * @return boolean
1914
	 */
1915
	public function hasGroupActions()
1916
	{
1917
		return (bool) $this->group_action_collection;
1918
	}
1919
1920
1921
	/********************************************************************************
1922
	 *                                   HANDLERS                                   *
1923
	 ********************************************************************************/
1924
1925
1926
	/**
1927
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1928
	 * @param  int  $page
1929
	 * @return void
1930
	 */
1931
	public function handlePage($page)
1932
	{
1933
		/**
1934
		 * Session stuff
1935
		 */
1936
		$this->page = $page;
1937
		$this->saveSessionData('_grid_page', $page);
1938
1939
		$this->reload(['table']);
1940
	}
1941
1942
1943
	/**
1944
	 * Handler for sorting
1945
	 * @param array $sort
1946
	 * @return void
1947
	 */
1948
	public function handleSort(array $sort)
1949
	{
1950
		$new_sort = [];
1951
1952
		/**
1953
		 * Find apropirate column
1954
		 */
1955
		foreach ($sort as $key => $value) {
1956
			if (empty($this->columns[$key])) {
1957
				throw new DataGridException("Column <$key> not found");
1958
			}
1959
1960
			$column = $this->columns[$key];
1961
			$new_sort = [$key => $value];
1962
1963
			/**
1964
			 * Pagination may be reseted after sorting
1965
			 */
1966
			if ($column->sortableResetPagination()) {
1967
				$this->page = 1;
1968
				$this->saveSessionData('_grid_page', 1);
1969
			}
1970
1971
			/**
1972
			 * Custom sorting callback may be applied
1973
			 */
1974
			if ($column->getSortableCallback()) {
1975
				$this->sort_callback = $column->getSortableCallback();
1976
			}
1977
		}
1978
1979
		/**
1980
		 * Session stuff
1981
		 */
1982
		$this->sort = $new_sort;
1983
		$this->saveSessionData('_grid_sort', $this->sort);
1984
1985
		$this->reload(['table']);
1986
	}
1987
1988
1989
	/**
1990
	 * Handler for reseting the filter
1991
	 * @return void
1992
	 */
1993
	public function handleResetFilter()
1994
	{
1995
		/**
1996
		 * Session stuff
1997
		 */
1998
		$this->deleteSesssionData('_grid_page');
1999
2000
		if ($this->default_filter_use_on_reset) {
2001
			$this->deleteSesssionData('_grid_has_filtered');
2002
		}
2003
2004
		foreach ($this->getSessionData() as $key => $value) {
2005
			if (!in_array($key, [
2006
				'_grid_per_page',
2007
				'_grid_sort',
2008
				'_grid_page',
2009
				'_grid_has_filtered',
2010
				'_grid_hidden_columns',
2011
				'_grid_hidden_columns_manipulated'
2012
				])) {
2013
2014
				$this->deleteSesssionData($key);
2015
			}
2016
		}
2017
2018
		$this->filter = [];
2019
2020
		$this->reload(['grid']);
2021
	}
2022
2023
2024
	/**
2025
	 * @param  string $key
2026
	 * @return void
2027
	 */
2028
	public function handleResetColumnFilter($key)
2029
	{
2030
		$this->deleteSesssionData($key);
2031
		unset($this->filter[$key]);
2032
2033
		$this->reload(['grid']);
2034
	}
2035
2036
2037
	/**
2038
	 * @param bool $reset
2039
	 * @return static
2040
	 */
2041
	public function setColumnReset($reset = TRUE)
2042
	{
2043
		$this->has_column_reset = (bool) $reset;
2044
2045
		return $this;
2046
	}
2047
2048
2049
	/**
2050
	 * @return bool
2051
	 */
2052
	public function hasColumnReset()
2053
	{
2054
		return $this->has_column_reset;
2055
	}
2056
2057
2058
	/**
2059
	 * @param  Filter\Filter[] $filters
2060
	 * @return void
2061
	 */
2062
	public function sendNonEmptyFiltersInPayload($filters)
2063
	{
2064
		if (!$this->hasColumnReset()) {
2065
			return;
2066
		}
2067
2068
		$non_empty_filters = [];
2069
2070
		foreach ($filters as $filter) {
2071
			if ($filter->isValueSet()) {
2072
				$non_empty_filters[] = $filter->getKey();
2073
			}
2074
		}
2075
2076
		$this->getPresenter()->payload->non_empty_filters = $non_empty_filters;
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2077
	}
2078
2079
2080
	/**
2081
	 * Handler for export
2082
	 * @param  int $id Key for particular export class in array $this->exports
2083
	 * @return void
2084
	 */
2085
	public function handleExport($id)
2086
	{
2087
		if (!isset($this->exports[$id])) {
2088
			throw new Nette\Application\ForbiddenRequestException;
2089
		}
2090
2091
		if (!empty($this->columns_export_order)) {
2092
			$this->setColumnsOrder($this->columns_export_order);
2093
		}
2094
2095
		$export = $this->exports[$id];
2096
2097
		/**
2098
		 * Invoke possible events
2099
		 */
2100
		$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...
2101
2102
		if ($export->isFiltered()) {
2103
			$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...
2104
			$filter    = $this->assableFilters();
2105
		} else {
2106
			$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...
2107
			$filter    = [];
2108
		}
2109
2110
		if (NULL === $this->dataModel) {
2111
			throw new DataGridException('You have to set a data source first.');
2112
		}
2113
2114
		$rows = [];
2115
2116
		$items = Nette\Utils\Callback::invokeArgs(
2117
			[$this->dataModel, 'filterData'], [
2118
				NULL,
2119
				$this->createSorting($this->sort, $this->sort_callback),
2120
				$filter
2121
			]
2122
		);
2123
2124
		foreach ($items as $item) {
2125
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
2126
		}
2127
2128
		if ($export instanceof Export\ExportCsv) {
2129
			$export->invoke($rows);
2130
		} else {
2131
			$export->invoke($items);
2132
		}
2133
2134
		if ($export->isAjax()) {
2135
			$this->reload();
2136
		}
2137
	}
2138
2139
2140
	/**
2141
	 * Handler for getting children of parent item (e.g. category)
2142
	 * @param  int $parent
2143
	 * @return void
2144
	 */
2145
	public function handleGetChildren($parent)
2146
	{
2147
		$this->setDataSource(
2148
			call_user_func($this->tree_view_children_callback, $parent)
2149
		);
2150
2151
		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...
2152
			$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...
2153
			$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...
2154
2155
			$this->redrawControl('items');
2156
2157
			$this->onRedraw();
2158
		} else {
2159
			$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...
2160
		}
2161
	}
2162
2163
2164
	/**
2165
	 * Handler for getting item detail
2166
	 * @param  mixed $id
2167
	 * @return void
2168
	 */
2169
	public function handleGetItemDetail($id)
2170
	{
2171
		$this->getTemplate()->add('toggle_detail', $id);
2172
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
2173
2174
		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...
2175
			$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...
2176
			$this->redrawControl('items');
2177
2178
			/**
2179
			 * Only for nette 2.4
2180
			 */
2181
			if (method_exists($this->getTemplate()->getLatte(), 'addProvider')) {
2182
				$this->redrawControl('gridSnippets');
2183
			}
2184
2185
			$this->onRedraw();
2186
		} else {
2187
			$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...
2188
		}
2189
	}
2190
2191
2192
	/**
2193
	 * Handler for inline editing
2194
	 * @param  mixed $id
2195
	 * @param  mixed $key
2196
	 * @return void
2197
	 */
2198
	public function handleEdit($id, $key)
2199
	{
2200
		$column = $this->getColumn($key);
2201
		$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...
2202
2203
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
2204
	}
2205
2206
2207
	/**
2208
	 * Redraw $this
2209
	 * @return void
2210
	 */
2211
	public function reload($snippets = [])
2212
	{
2213
		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...
2214
			$this->redrawControl('tbody');
2215
			$this->redrawControl('pagination');
2216
2217
			/**
2218
			 * manualy reset exports links...
2219
			 */
2220
			$this->resetExportsLinks();
2221
			$this->redrawControl('exports');
2222
2223
			foreach ($snippets as $snippet) {
2224
				$this->redrawControl($snippet);
2225
			}
2226
2227
			$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...
2228
			$this->getPresenter()->payload->_datagrid_name = $this->getName();
0 ignored issues
show
Bug introduced by
Accessing payload on the interface Nette\ComponentModel\IComponent suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2229
2230
			$this->onRedraw();
2231
		} else {
2232
			$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...
2233
		}
2234
	}
2235
2236
2237
	/**
2238
	 * Handler for column status
2239
	 * @param  string $id
2240
	 * @param  string $key
2241
	 * @param  string $value
2242
	 * @return void
2243
	 */
2244
	public function handleChangeStatus($id, $key, $value)
2245
	{
2246
		if (empty($this->columns[$key])) {
2247
			throw new DataGridException("ColumnStatus[$key] does not exist");
2248
		}
2249
2250
		$this->columns[$key]->onChange($id, $value);
2251
	}
2252
2253
2254
	/**
2255
	 * Redraw just one row via ajax
2256
	 * @param  int   $id
2257
	 * @param  mixed $primary_where_column
2258
	 * @return void
2259
	 */
2260
	public function redrawItem($id, $primary_where_column = NULL)
2261
	{
2262
		$this->snippets_set = TRUE;
2263
2264
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
2265
2266
		$this->redrawControl('items');
2267
2268
		$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...
2269
2270
		$this->onRedraw();
2271
	}
2272
2273
2274
	/**
2275
	 * Tell datagrid to display all columns
2276
	 * @return void
2277
	 */
2278
	public function handleShowAllColumns()
2279
	{
2280
		$this->deleteSesssionData('_grid_hidden_columns');
2281
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2282
2283
		$this->redrawControl();
2284
2285
		$this->onRedraw();
2286
	}
2287
2288
2289
	/**
2290
	 * Tell datagrid to display default columns
2291
	 * @return void
2292
	 */
2293
	public function handleShowDefaultColumns()
2294
	{
2295
		$this->deleteSesssionData('_grid_hidden_columns');
2296
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
2297
2298
		$this->redrawControl();
2299
2300
		$this->onRedraw();
2301
	}
2302
2303
2304
	/**
2305
	 * Reveal particular column
2306
	 * @param  string $column
2307
	 * @return void
2308
	 */
2309 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...
2310
	{
2311
		$columns = $this->getSessionData('_grid_hidden_columns');
2312
2313
		if (!empty($columns)) {
2314
			$pos = array_search($column, $columns);
2315
2316
			if ($pos !== FALSE) {
2317
				unset($columns[$pos]);
2318
			}
2319
		}
2320
2321
		$this->saveSessionData('_grid_hidden_columns', $columns);
2322
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2323
2324
		$this->redrawControl();
2325
2326
		$this->onRedraw();
2327
	}
2328
2329
2330
	/**
2331
	 * Notice datagrid to not display particular columns
2332
	 * @param  string $column
2333
	 * @return void
2334
	 */
2335 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...
2336
	{
2337
		/**
2338
		 * Store info about hiding a column to session
2339
		 */
2340
		$columns = $this->getSessionData('_grid_hidden_columns');
2341
2342
		if (empty($columns)) {
2343
			$columns = [$column];
2344
		} else if (!in_array($column, $columns)) {
2345
			array_push($columns, $column);
2346
		}
2347
2348
		$this->saveSessionData('_grid_hidden_columns', $columns);
2349
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2350
2351
		$this->redrawControl();
2352
2353
		$this->onRedraw();
2354
	}
2355
2356
2357
	public function handleActionCallback($__key, $__id)
2358
	{
2359
		$action = $this->getAction($__key);
2360
2361
		if (!($action instanceof Column\ActionCallback)) {
2362
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2363
		}
2364
2365
		$action->onClick($__id);
2366
	}
2367
2368
2369
	/********************************************************************************
2370
	 *                                  PAGINATION                                  *
2371
	 ********************************************************************************/
2372
2373
2374
	/**
2375
	 * Set options of select "items_per_page"
2376
	 * @param array $items_per_page_list
2377
	 * @return static
2378
	 */
2379
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
2380
	{
2381
		$this->items_per_page_list = $items_per_page_list;
2382
2383
		if ($include_all) {
2384
			$this->items_per_page_list[] = 'all';
2385
		}
2386
2387
		return $this;
2388
	}
2389
2390
2391
	/**
2392
	 * Set default "items per page" value in pagination select
2393
	 * @param $count
2394
	 * @return static
2395
	 */
2396
	public function setDefaultPerPage($count)
2397
	{
2398
		$this->default_per_page = $count;
2399
2400
		return $this;
2401
	}
2402
2403
2404
	/**
2405
	 * User may set default "items per page" value, apply it
2406
	 * @return void
2407
	 */
2408
	public function findDefaultPerPage()
2409
	{
2410
		if (!empty($this->per_page)) {
2411
			return;
2412
		}
2413
2414
		if (!empty($this->default_per_page)) {
2415
			$this->per_page = $this->default_per_page;
2416
		}
2417
2418
		$this->saveSessionData('_grid_per_page', $this->per_page);
2419
	}
2420
2421
2422
	/**
2423
	 * Paginator factory
2424
	 * @return Components\DataGridPaginator\DataGridPaginator
2425
	 */
2426
	public function createComponentPaginator()
2427
	{
2428
		/**
2429
		 * Init paginator
2430
		 */
2431
		$component = new Components\DataGridPaginator\DataGridPaginator(
2432
			$this->getTranslator(),
2433
			static::$icon_prefix
2434
		);
2435
		$paginator = $component->getPaginator();
2436
2437
		$paginator->setPage($this->page);
2438
		$paginator->setItemsPerPage($this->getPerPage());
2439
2440
		return $component;
2441
	}
2442
2443
2444
	/**
2445
	 * Get parameter per_page
2446
	 * @return int
2447
	 */
2448
	public function getPerPage()
2449
	{
2450
		$items_per_page_list = $this->getItemsPerPageList();
2451
2452
		$per_page = $this->per_page ?: reset($items_per_page_list);
2453
2454
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2455
			$per_page = reset($items_per_page_list);
2456
		}
2457
2458
		return $per_page;
2459
	}
2460
2461
2462
	/**
2463
	 * Get associative array of items_per_page_list
2464
	 * @return array
2465
	 */
2466
	public function getItemsPerPageList()
2467
	{
2468
		if (empty($this->items_per_page_list)) {
2469
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2470
		}
2471
2472
		$list = array_flip($this->items_per_page_list);
2473
2474
		foreach ($list as $key => $value) {
2475
			$list[$key] = $key;
2476
		}
2477
2478
		if (array_key_exists('all', $list)) {
2479
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2480
		}
2481
2482
		return $list;
2483
	}
2484
2485
2486
	/**
2487
	 * Order Grid to "be paginated"
2488
	 * @param bool $do
2489
	 * @return static
2490
	 */
2491
	public function setPagination($do)
2492
	{
2493
		$this->do_paginate = (bool) $do;
2494
2495
		return $this;
2496
	}
2497
2498
2499
	/**
2500
	 * Tell whether Grid is paginated
2501
	 * @return bool
2502
	 */
2503
	public function isPaginated()
2504
	{
2505
		return $this->do_paginate;
2506
	}
2507
2508
2509
	/**
2510
	 * Return current paginator class
2511
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2512
	 */
2513
	public function getPaginator()
2514
	{
2515
		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...
2516
			return $this['paginator'];
2517
		}
2518
2519
		return NULL;
2520
	}
2521
2522
2523
	/********************************************************************************
2524
	 *                                     I18N                                     *
2525
	 ********************************************************************************/
2526
2527
2528
	/**
2529
	 * Set datagrid translator
2530
	 * @param Nette\Localization\ITranslator $translator
2531
	 * @return static
2532
	 */
2533
	public function setTranslator(Nette\Localization\ITranslator $translator)
2534
	{
2535
		$this->translator = $translator;
2536
2537
		return $this;
2538
	}
2539
2540
2541
	/**
2542
	 * Get translator for datagrid
2543
	 * @return Nette\Localization\ITranslator
2544
	 */
2545
	public function getTranslator()
2546
	{
2547
		if (!$this->translator) {
2548
			$this->translator = new Localization\SimpleTranslator;
2549
		}
2550
2551
		return $this->translator;
2552
	}
2553
2554
2555
	/********************************************************************************
2556
	 *                                 COLUMNS ORDER                                *
2557
	 ********************************************************************************/
2558
2559
2560
	/**
2561
	 * Set order of datagrid columns
2562
	 * @param array $order
2563
	 * @return static
2564
	 */
2565
	public function setColumnsOrder($order)
2566
	{
2567
		$new_order = [];
2568
2569
		foreach ($order as $key) {
2570
			if (isset($this->columns[$key])) {
2571
				$new_order[$key] = $this->columns[$key];
2572
			}
2573
		}
2574
2575
		if (sizeof($new_order) === sizeof($this->columns)) {
2576
			$this->columns = $new_order;
2577
		} else {
2578
			throw new DataGridException('When changing columns order, you have to specify all columns');
2579
		}
2580
2581
		return $this;
2582
	}
2583
2584
2585
	/**
2586
	 * Columns order may be different for export and normal grid
2587
	 * @param array $order
2588
	 */
2589
	public function setColumnsExportOrder($order)
2590
	{
2591
		$this->columns_export_order = (array) $order;
2592
	}
2593
2594
2595
	/********************************************************************************
2596
	 *                                SESSION & URL                                 *
2597
	 ********************************************************************************/
2598
2599
2600
	/**
2601
	 * Find some unique session key name
2602
	 * @return string
2603
	 */
2604
	public function getSessionSectionName()
2605
	{
2606
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2607
	}
2608
2609
2610
	/**
2611
	 * Should datagrid remember its filters/pagination/etc using session?
2612
	 * @param bool $remember
2613
	 * @return static
2614
	 */
2615
	public function setRememberState($remember = TRUE)
2616
	{
2617
		$this->remember_state = (bool) $remember;
2618
2619
		return $this;
2620
	}
2621
2622
2623
	/**
2624
	 * Should datagrid refresh url using history API?
2625
	 * @param bool $refresh
2626
	 * @return static
2627
	 */
2628
	public function setRefreshUrl($refresh = TRUE)
2629
	{
2630
		$this->refresh_url = (bool) $refresh;
2631
2632
2633
		return $this;
2634
	}
2635
2636
2637
	/**
2638
	 * Get session data if functionality is enabled
2639
	 * @param  string $key
2640
	 * @return mixed
2641
	 */
2642
	public function getSessionData($key = NULL, $default_value = NULL)
2643
	{
2644
		if (!$this->remember_state) {
2645
			return $key ? $default_value : [];
2646
		}
2647
2648
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2649
	}
2650
2651
2652
	/**
2653
	 * Save session data - just if it is enabled
2654
	 * @param  string $key
2655
	 * @param  mixed  $value
2656
	 * @return void
2657
	 */
2658
	public function saveSessionData($key, $value)
2659
	{
2660
		if ($this->remember_state) {
2661
			$this->grid_session->{$key} = $value;
2662
		}
2663
	}
2664
2665
2666
	/**
2667
	 * Delete session data
2668
	 * @return void
2669
	 */
2670
	public function deleteSesssionData($key)
2671
	{
2672
		unset($this->grid_session->{$key});
2673
	}
2674
2675
2676
	/********************************************************************************
2677
	 *                                  ITEM DETAIL                                 *
2678
	 ********************************************************************************/
2679
2680
2681
	/**
2682
	 * Get items detail parameters
2683
	 * @return array
2684
	 */
2685
	public function getItemsDetail()
2686
	{
2687
		return $this->items_detail;
2688
	}
2689
2690
2691
	/**
2692
	 * Items can have thair detail - toggled
2693
	 * @param mixed $detail callable|string|bool
2694
	 * @param bool|NULL $primary_where_column
2695
	 * @return Column\ItemDetail
2696
	 */
2697
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2698
	{
2699
		if ($this->isSortable()) {
2700
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2701
		}
2702
2703
		$this->items_detail = new Column\ItemDetail(
2704
			$this,
2705
			$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...
2706
		);
2707
2708
		if (is_string($detail)) {
2709
			/**
2710
			 * Item detail will be in separate template
2711
			 */
2712
			$this->items_detail->setType('template');
2713
			$this->items_detail->setTemplate($detail);
2714
2715
		} else if (is_callable($detail)) {
2716
			/**
2717
			 * Item detail will be rendered via custom callback renderer
2718
			 */
2719
			$this->items_detail->setType('renderer');
2720
			$this->items_detail->setRenderer($detail);
2721
2722
		} else if (TRUE === $detail) {
2723
			/**
2724
			 * Item detail will be rendered probably via block #detail
2725
			 */
2726
			$this->items_detail->setType('block');
2727
2728
		} else {
2729
			throw new DataGridException(
2730
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2731
			);
2732
		}
2733
2734
		return $this->items_detail;
2735
	}
2736
2737
2738
	/**
2739
	 * @param callable $callable_set_container 
2740
	 * @return static
2741
	 */
2742
	public function setItemsDetailForm(callable $callable_set_container)
2743
	{
2744
		if ($this->items_detail instanceof Column\ItemDetail) {
2745
			$this->items_detail->setForm(
2746
				new Utils\ItemDetailForm($callable_set_container)
2747
			);
2748
2749
			return $this;
2750
		}
2751
2752
		throw new DataGridException('Please set the ItemDetail first.');
2753
	}
2754
2755
2756
	/**
2757
	 * @return Nette\Forms\Container|NULL
2758
	 */
2759
	public function getItemDetailForm()
2760
	{
2761
		if ($this->items_detail instanceof Column\ItemDetail) {
2762
			return $this->items_detail->getForm();
2763
		}
2764
2765
		return NULL;
2766
	}
2767
2768
2769
	/********************************************************************************
2770
	 *                                ROW PRIVILEGES                                *
2771
	 ********************************************************************************/
2772
2773
2774
	/**
2775
	 * @param  callable $condition
2776
	 * @return void
2777
	 */
2778
	public function allowRowsGroupAction(callable $condition)
2779
	{
2780
		$this->row_conditions['group_action'] = $condition;
2781
	}
2782
2783
2784
	/**
2785
	 * @param  callable $condition
2786
	 * @return void
2787
	 */
2788
	public function allowRowsInlineEdit(callable $condition)
2789
	{
2790
		$this->row_conditions['inline_edit'] = $condition;
2791
	}
2792
2793
2794
	/**
2795
	 * @param  string   $key
2796
	 * @param  callable $condition
2797
	 * @return void
2798
	 */
2799
	public function allowRowsAction($key, callable $condition)
2800
	{
2801
		$this->row_conditions['action'][$key] = $condition;
2802
	}
2803
2804
2805
	/**
2806
	 * @param  string      $name
2807
	 * @param  string|null $key
2808
	 * @return bool|callable
2809
	 */
2810
	public function getRowCondition($name, $key = NULL)
2811
	{
2812
		if (!isset($this->row_conditions[$name])) {
2813
			return FALSE;
2814
		}
2815
2816
		$condition = $this->row_conditions[$name];
2817
2818
		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...
2819
			return $condition;
2820
		}
2821
2822
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2823
	}
2824
2825
2826
	/********************************************************************************
2827
	 *                               COLUMN CALLBACK                                *
2828
	 ********************************************************************************/
2829
2830
2831
	/**
2832
	 * @param  string   $key
2833
	 * @param  callable $callback
2834
	 * @return void
2835
	 */
2836
	public function addColumnCallback($key, callable $callback)
2837
	{
2838
		$this->column_callbacks[$key] = $callback;
2839
	}
2840
2841
2842
	/**
2843
	 * @param  string $key
2844
	 * @return callable|null
2845
	 */
2846
	public function getColumnCallback($key)
2847
	{
2848
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2849
	}
2850
2851
2852
	/********************************************************************************
2853
	 *                                 INLINE EDIT                                  *
2854
	 ********************************************************************************/
2855
2856
2857
	/**
2858
	 * @return InlineEdit
2859
	 */
2860
	public function addInlineEdit($primary_where_column = NULL)
2861
	{
2862
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2863
2864
		return $this->inlineEdit;
2865
	}
2866
2867
2868
	/**
2869
	 * @return InlineEdit|null
2870
	 */
2871
	public function getInlineEdit()
2872
	{
2873
		return $this->inlineEdit;
2874
	}
2875
2876
2877
	/**
2878
	 * @param  mixed $id
2879
	 * @return void
2880
	 */
2881
	public function handleInlineEdit($id)
2882
	{
2883
		if ($this->inlineEdit) {
2884
			$this->inlineEdit->setItemId($id);
2885
2886
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2887
2888
			$this['filter']['inline_edit']->addHidden('_id', $id);
2889
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2890
2891
			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...
2892
				$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...
2893
			}
2894
2895
			$this->redrawItem($id, $primary_where_column);
2896
		}
2897
	}
2898
2899
2900
	/********************************************************************************
2901
	 *                                  INLINE ADD                                  *
2902
	 ********************************************************************************/
2903
2904
2905
	/**
2906
	 * @return InlineEdit
2907
	 */
2908
	public function addInlineAdd()
2909
	{
2910
		$this->inlineAdd = new InlineEdit($this);
2911
2912
		$this->inlineAdd
2913
			->setTitle('ublaboo_datagrid.add')
2914
			->setIcon('plus')
2915
			->setClass('btn btn-xs btn-default');
2916
2917
		return $this->inlineAdd;
2918
	}
2919
2920
2921
	/**
2922
	 * @return InlineEdit|null
2923
	 */
2924
	public function getInlineAdd()
2925
	{
2926
		return $this->inlineAdd;
2927
	}
2928
2929
2930
	/********************************************************************************
2931
	 *                               HIDEABLE COLUMNS                               *
2932
	 ********************************************************************************/
2933
2934
2935
	/**
2936
	 * Can datagrid hide colums?
2937
	 * @return boolean
2938
	 */
2939
	public function canHideColumns()
2940
	{
2941
		return (bool) $this->can_hide_columns;
2942
	}
2943
2944
2945
	/**
2946
	 * Order Grid to set columns hideable.
2947
	 * @return static
2948
	 */
2949
	public function setColumnsHideable()
2950
	{
2951
		$this->can_hide_columns = TRUE;
2952
2953
		return $this;
2954
	}
2955
2956
2957
	/********************************************************************************
2958
	 *                                COLUMNS SUMMARY                               *
2959
	 ********************************************************************************/
2960
2961
2962
	/**
2963
	 * Will datagrid show summary in the end?
2964
	 * @return bool
2965
	 */
2966
	public function hasColumnsSummary()
2967
	{
2968
		return $this->columnsSummary instanceof ColumnsSummary;
2969
	}
2970
2971
2972
	/**
2973
	 * Set columns to be summarized in the end.
2974
	 * @param array    $columns
2975
	 * @param callable $rowCallback
2976
	 * @return \Ublaboo\DataGrid\ColumnsSummary
2977
	 */
2978
	public function setColumnsSummary(array $columns, $rowCallback = NULL)
2979
	{
2980
		if (!empty($rowCallback)) {
2981
			if (!is_callable($rowCallback)) {
2982
				throw new \InvalidArgumentException('Row summary callback must be callable');
2983
			}
2984
		}
2985
2986
		$this->columnsSummary = new ColumnsSummary($this, $columns, $rowCallback);
2987
2988
		return $this->columnsSummary;
2989
	}
2990
2991
2992
	/**
2993
	 * @return ColumnsSummary|NULL
2994
	 */
2995
	public function getColumnsSummary()
2996
	{
2997
		return $this->columnsSummary;
2998
	}
2999
3000
3001
	/********************************************************************************
3002
	 *                                   INTERNAL                                   *
3003
	 ********************************************************************************/
3004
3005
3006
	/**
3007
	 * Tell grid filters to by submitted automatically
3008
	 * @param bool $auto
3009
	 */
3010
	public function setAutoSubmit($auto = TRUE)
3011
	{
3012
		$this->auto_submit = (bool) $auto;
3013
3014
		return $this;
3015
	}
3016
3017
3018
	/**
3019
	 * @return bool
3020
	 */
3021
	public function hasAutoSubmit()
3022
	{
3023
		return $this->auto_submit;
3024
	}
3025
3026
3027
	/**
3028
	 * Submit button when no auto-submitting is used
3029
	 * @return Filter\SubmitButton
3030
	 */
3031
	public function getFilterSubmitButton()
3032
	{
3033
		if ($this->hasAutoSubmit()) {
3034
			throw new DataGridException(
3035
				'DataGrid has auto-submit. Turn it off before setting filter submit button.'
3036
			);
3037
		}
3038
3039
		if ($this->filter_submit_button === NULL) {
3040
			$this->filter_submit_button = new Filter\SubmitButton($this);
3041
		}
3042
3043
		return $this->filter_submit_button;
3044
	}
3045
3046
3047
	/********************************************************************************
3048
	 *                                   INTERNAL                                   *
3049
	 ********************************************************************************/
3050
3051
3052
	/**
3053
	 * Get count of columns
3054
	 * @return int
3055
	 */
3056
	public function getColumnsCount()
3057
	{
3058
		$count = sizeof($this->getColumns());
3059
3060
		if (!empty($this->actions)
3061
			|| $this->isSortable()
3062
			|| $this->getItemsDetail()
3063
			|| $this->getInlineEdit()
3064
			|| $this->getInlineAdd()) {
3065
			$count++;
3066
		}
3067
3068
		if ($this->hasGroupActions()) {
3069
			$count++;
3070
		}
3071
3072
		return $count;
3073
	}
3074
3075
3076
	/**
3077
	 * Get primary key of datagrid data source
3078
	 * @return string
3079
	 */
3080
	public function getPrimaryKey()
3081
	{
3082
		return $this->primary_key;
3083
	}
3084
3085
3086
	/**
3087
	 * Get set of set columns
3088
	 * @return Column\IColumn[]
3089
	 */
3090
	public function getColumns()
3091
	{
3092
		$return = $this->columns;
3093
3094
		try {
3095
			$this->getParent();
3096
3097
			if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
3098
				$columns_to_hide = [];
3099
3100
				foreach ($this->columns as $key => $column) {
3101
					if ($column->getDefaultHide()) {
3102
						$columns_to_hide[] = $key;
3103
					}
3104
				}
3105
3106
				if (!empty($columns_to_hide)) {
3107
					$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
3108
					$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
3109
				}
3110
			}
3111
3112
			$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
3113
3114
			foreach ($hidden_columns as $column) {
3115
				if (!empty($this->columns[$column])) {
3116
					$this->columns_visibility[$column] = [
3117
						'visible' => FALSE
3118
					];
3119
3120
					unset($return[$column]);
3121
				}
3122
			}
3123
3124
		} catch (DataGridHasToBeAttachedToPresenterComponentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
3125
		}
3126
3127
		return $return;
3128
	}
3129
3130
3131
	public function getColumnsVisibility()
3132
	{
3133
		$return = $this->columns_visibility;
3134
3135
		foreach ($this->columns_visibility as $key => $column) {
3136
			$return[$key]['column'] = $this->columns[$key];
3137
		}
3138
3139
		return $return;
3140
	}
3141
3142
3143
	/**
3144
	 * @return PresenterComponent
3145
	 */
3146
	public function getParent()
3147
	{
3148
		$parent = parent::getParent();
3149
3150
		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...
3151
			throw new DataGridHasToBeAttachedToPresenterComponentException(
3152
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
3153
			);
3154
		}
3155
3156
		return $parent;
3157
	}
3158
3159
3160
	/**
3161
	 * @return strign
3162
	 */
3163
	public function getSortableParentPath()
3164
	{
3165
		return $this->getParent()->lookupPath(Nette\Application\UI\Control::class, FALSE);
3166
	}
3167
3168
3169
	/**
3170
	 * Some of datagrid columns is hidden by default
3171
	 * @param bool $default_hide
3172
	 */
3173
	public function setSomeColumnDefaultHide($default_hide)
3174
	{
3175
		$this->some_column_default_hide = $default_hide;
3176
	}
3177
3178
3179
	/**
3180
	 * Are some of columns hidden bydefault?
3181
	 */
3182
	public function hasSomeColumnDefaultHide()
3183
	{
3184
		return $this->some_column_default_hide;
3185
	}
3186
3187
3188
	/**
3189
	 * Simply refresh url
3190
	 * @return void
3191
	 */
3192
	public function handleRefreshState()
3193
	{
3194
		$this->findSessionValues();
3195
		$this->findDefaultFilter();
3196
		$this->findDefaultSort();
3197
		$this->findDefaultPerPage();
3198
3199
		$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...
3200
		$this->redrawControl('non-existing-snippet');
3201
	}
3202
3203
}
3204