Completed
Pull Request — master (#409)
by Dalibor
03:31
created

DataGrid::getColumns()   C

Complexity

Conditions 8
Paths 66

Size

Total Lines 39
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2722
		return $this->deleteSessionData($key);
2723
	}
2724
2725
2726
	/********************************************************************************
2727
	 *                                  ITEM DETAIL                                 *
2728
	 ********************************************************************************/
2729
2730
2731
	/**
2732
	 * Get items detail parameters
2733
	 * @return array
2734
	 */
2735
	public function getItemsDetail()
2736
	{
2737
		return $this->items_detail;
2738
	}
2739
2740
2741
	/**
2742
	 * Items can have thair detail - toggled
2743
	 * @param mixed $detail callable|string|bool
2744
	 * @param bool|NULL $primary_where_column
2745
	 * @return Column\ItemDetail
2746
	 */
2747
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2748
	{
2749
		if ($this->isSortable()) {
2750
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2751
		}
2752
2753
		$this->items_detail = new Column\ItemDetail(
2754
			$this,
2755
			$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...
2756
		);
2757
2758
		if (is_string($detail)) {
2759
			/**
2760
			 * Item detail will be in separate template
2761
			 */
2762
			$this->items_detail->setType('template');
2763
			$this->items_detail->setTemplate($detail);
2764
2765
		} else if (is_callable($detail)) {
2766
			/**
2767
			 * Item detail will be rendered via custom callback renderer
2768
			 */
2769
			$this->items_detail->setType('renderer');
2770
			$this->items_detail->setRenderer($detail);
2771
2772
		} else if (TRUE === $detail) {
2773
			/**
2774
			 * Item detail will be rendered probably via block #detail
2775
			 */
2776
			$this->items_detail->setType('block');
2777
2778
		} else {
2779
			throw new DataGridException(
2780
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2781
			);
2782
		}
2783
2784
		return $this->items_detail;
2785
	}
2786
2787
2788
	/**
2789
	 * @param callable $callable_set_container 
2790
	 * @return static
2791
	 */
2792
	public function setItemsDetailForm(callable $callable_set_container)
2793
	{
2794
		if ($this->items_detail instanceof Column\ItemDetail) {
2795
			$this->items_detail->setForm(
2796
				new Utils\ItemDetailForm($callable_set_container)
2797
			);
2798
2799
			return $this;
2800
		}
2801
2802
		throw new DataGridException('Please set the ItemDetail first.');
2803
	}
2804
2805
2806
	/**
2807
	 * @return Nette\Forms\Container|NULL
2808
	 */
2809
	public function getItemDetailForm()
2810
	{
2811
		if ($this->items_detail instanceof Column\ItemDetail) {
2812
			return $this->items_detail->getForm();
2813
		}
2814
2815
		return NULL;
2816
	}
2817
2818
2819
	/********************************************************************************
2820
	 *                                ROW PRIVILEGES                                *
2821
	 ********************************************************************************/
2822
2823
2824
	/**
2825
	 * @param  callable $condition
2826
	 * @return void
2827
	 */
2828
	public function allowRowsGroupAction(callable $condition)
2829
	{
2830
		$this->row_conditions['group_action'] = $condition;
2831
	}
2832
2833
2834
	/**
2835
	 * @param  callable $condition
2836
	 * @return void
2837
	 */
2838
	public function allowRowsInlineEdit(callable $condition)
2839
	{
2840
		$this->row_conditions['inline_edit'] = $condition;
2841
	}
2842
2843
2844
	/**
2845
	 * @param  string   $key
2846
	 * @param  callable $condition
2847
	 * @return void
2848
	 */
2849
	public function allowRowsAction($key, callable $condition)
2850
	{
2851
		$this->row_conditions['action'][$key] = $condition;
2852
	}
2853
2854
2855
	/**
2856
	 * @param  string      $name
2857
	 * @param  string|null $key
2858
	 * @return bool|callable
2859
	 */
2860
	public function getRowCondition($name, $key = NULL)
2861
	{
2862
		if (!isset($this->row_conditions[$name])) {
2863
			return FALSE;
2864
		}
2865
2866
		$condition = $this->row_conditions[$name];
2867
2868
		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...
2869
			return $condition;
2870
		}
2871
2872
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2873
	}
2874
2875
2876
	/********************************************************************************
2877
	 *                               COLUMN CALLBACK                                *
2878
	 ********************************************************************************/
2879
2880
2881
	/**
2882
	 * @param  string   $key
2883
	 * @param  callable $callback
2884
	 * @return void
2885
	 */
2886
	public function addColumnCallback($key, callable $callback)
2887
	{
2888
		$this->column_callbacks[$key] = $callback;
2889
	}
2890
2891
2892
	/**
2893
	 * @param  string $key
2894
	 * @return callable|null
2895
	 */
2896
	public function getColumnCallback($key)
2897
	{
2898
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2899
	}
2900
2901
2902
	/********************************************************************************
2903
	 *                                 INLINE EDIT                                  *
2904
	 ********************************************************************************/
2905
2906
2907
	/**
2908
	 * @return InlineEdit
2909
	 */
2910
	public function addInlineEdit($primary_where_column = NULL)
2911
	{
2912
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2913
2914
		return $this->inlineEdit;
2915
	}
2916
2917
2918
	/**
2919
	 * @return InlineEdit|null
2920
	 */
2921
	public function getInlineEdit()
2922
	{
2923
		return $this->inlineEdit;
2924
	}
2925
2926
2927
	/**
2928
	 * @param  mixed $id
2929
	 * @return void
2930
	 */
2931
	public function handleInlineEdit($id)
2932
	{
2933
		if ($this->inlineEdit) {
2934
			$this->inlineEdit->setItemId($id);
2935
2936
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2937
2938
			$this['filter']['inline_edit']->addHidden('_id', $id);
2939
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2940
2941
			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...
2942
				$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...
2943
			}
2944
2945
			$this->redrawItem($id, $primary_where_column);
2946
		}
2947
	}
2948
2949
2950
	/********************************************************************************
2951
	 *                                  INLINE ADD                                  *
2952
	 ********************************************************************************/
2953
2954
2955
	/**
2956
	 * @return InlineEdit
2957
	 */
2958
	public function addInlineAdd()
2959
	{
2960
		$this->inlineAdd = new InlineEdit($this);
2961
2962
		$this->inlineAdd
2963
			->setTitle('ublaboo_datagrid.add')
2964
			->setIcon('plus')
2965
			->setClass('btn btn-xs btn-default');
2966
2967
		return $this->inlineAdd;
2968
	}
2969
2970
2971
	/**
2972
	 * @return InlineEdit|null
2973
	 */
2974
	public function getInlineAdd()
2975
	{
2976
		return $this->inlineAdd;
2977
	}
2978
2979
2980
	/********************************************************************************
2981
	 *                               HIDEABLE COLUMNS                               *
2982
	 ********************************************************************************/
2983
2984
2985
	/**
2986
	 * Can datagrid hide colums?
2987
	 * @return boolean
2988
	 */
2989
	public function canHideColumns()
2990
	{
2991
		return (bool) $this->can_hide_columns;
2992
	}
2993
2994
2995
	/**
2996
	 * Order Grid to set columns hideable.
2997
	 * @return static
2998
	 */
2999
	public function setColumnsHideable()
3000
	{
3001
		$this->can_hide_columns = TRUE;
3002
3003
		return $this;
3004
	}
3005
3006
3007
	/********************************************************************************
3008
	 *                                COLUMNS SUMMARY                               *
3009
	 ********************************************************************************/
3010
3011
3012
	/**
3013
	 * Will datagrid show summary in the end?
3014
	 * @return bool
3015
	 */
3016
	public function hasColumnsSummary()
3017
	{
3018
		return $this->columnsSummary instanceof ColumnsSummary;
3019
	}
3020
3021
3022
	/**
3023
	 * Set columns to be summarized in the end.
3024
	 * @param array    $columns
3025
	 * @param callable $rowCallback
3026
	 * @return \Ublaboo\DataGrid\ColumnsSummary
3027
	 */
3028
	public function setColumnsSummary(array $columns, $rowCallback = NULL)
3029
	{
3030
		if (!empty($rowCallback)) {
3031
			if (!is_callable($rowCallback)) {
3032
				throw new \InvalidArgumentException('Row summary callback must be callable');
3033
			}
3034
		}
3035
3036
		$this->columnsSummary = new ColumnsSummary($this, $columns, $rowCallback);
3037
3038
		return $this->columnsSummary;
3039
	}
3040
3041
3042
	/**
3043
	 * @return ColumnsSummary|NULL
3044
	 */
3045
	public function getColumnsSummary()
3046
	{
3047
		return $this->columnsSummary;
3048
	}
3049
3050
3051
	/********************************************************************************
3052
	 *                                   INTERNAL                                   *
3053
	 ********************************************************************************/
3054
3055
3056
	/**
3057
	 * Tell grid filters to by submitted automatically
3058
	 * @param bool $auto
3059
	 */
3060
	public function setAutoSubmit($auto = TRUE)
3061
	{
3062
		$this->auto_submit = (bool) $auto;
3063
3064
		return $this;
3065
	}
3066
3067
3068
	/**
3069
	 * @return bool
3070
	 */
3071
	public function hasAutoSubmit()
3072
	{
3073
		return $this->auto_submit;
3074
	}
3075
3076
3077
	/**
3078
	 * Submit button when no auto-submitting is used
3079
	 * @return Filter\SubmitButton
3080
	 */
3081
	public function getFilterSubmitButton()
3082
	{
3083
		if ($this->hasAutoSubmit()) {
3084
			throw new DataGridException(
3085
				'DataGrid has auto-submit. Turn it off before setting filter submit button.'
3086
			);
3087
		}
3088
3089
		if ($this->filter_submit_button === NULL) {
3090
			$this->filter_submit_button = new Filter\SubmitButton($this);
3091
		}
3092
3093
		return $this->filter_submit_button;
3094
	}
3095
3096
3097
	/********************************************************************************
3098
	 *                                   INTERNAL                                   *
3099
	 ********************************************************************************/
3100
3101
3102
	/**
3103
	 * Get count of columns
3104
	 * @return int
3105
	 */
3106
	public function getColumnsCount()
3107
	{
3108
		$count = sizeof($this->getColumns());
3109
3110
		if (!empty($this->actions)
3111
			|| $this->isSortable()
3112
			|| $this->getItemsDetail()
3113
			|| $this->getInlineEdit()
3114
			|| $this->getInlineAdd()) {
3115
			$count++;
3116
		}
3117
3118
		if ($this->hasGroupActions()) {
3119
			$count++;
3120
		}
3121
3122
		return $count;
3123
	}
3124
3125
3126
	/**
3127
	 * Get primary key of datagrid data source
3128
	 * @return string
3129
	 */
3130
	public function getPrimaryKey()
3131
	{
3132
		return $this->primary_key;
3133
	}
3134
3135
3136
	/**
3137
	 * Get set of set columns
3138
	 * @return Column\IColumn[]
3139
	 */
3140
	public function getColumns()
3141
	{
3142
		$return = $this->columns;
3143
3144
		try {
3145
			$this->getParent();
3146
3147
			if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
3148
				$columns_to_hide = [];
3149
3150
				foreach ($this->columns as $key => $column) {
3151
					if ($column->getDefaultHide()) {
3152
						$columns_to_hide[] = $key;
3153
					}
3154
				}
3155
3156
				if (!empty($columns_to_hide)) {
3157
					$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
3158
					$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
3159
				}
3160
			}
3161
3162
			$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
3163
3164
			foreach ($hidden_columns as $column) {
3165
				if (!empty($this->columns[$column])) {
3166
					$this->columns_visibility[$column] = [
3167
						'visible' => FALSE
3168
					];
3169
3170
					unset($return[$column]);
3171
				}
3172
			}
3173
3174
		} catch (DataGridHasToBeAttachedToPresenterComponentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
3175
		}
3176
3177
		return $return;
3178
	}
3179
3180
3181
	public function getColumnsVisibility()
3182
	{
3183
		$return = $this->columns_visibility;
3184
3185
		foreach ($this->columns_visibility as $key => $column) {
3186
			$return[$key]['column'] = $this->columns[$key];
3187
		}
3188
3189
		return $return;
3190
	}
3191
3192
3193
	/**
3194
	 * @return PresenterComponent
3195
	 */
3196
	public function getParent()
3197
	{
3198
		$parent = parent::getParent();
3199
3200
		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...
3201
			throw new DataGridHasToBeAttachedToPresenterComponentException(
3202
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
3203
			);
3204
		}
3205
3206
		return $parent;
3207
	}
3208
3209
3210
	/**
3211
	 * @return strign
3212
	 */
3213
	public function getSortableParentPath()
3214
	{
3215
		return $this->getParent()->lookupPath(Nette\Application\UI\Control::class, FALSE);
3216
	}
3217
3218
3219
	/**
3220
	 * Some of datagrid columns is hidden by default
3221
	 * @param bool $default_hide
3222
	 */
3223
	public function setSomeColumnDefaultHide($default_hide)
3224
	{
3225
		$this->some_column_default_hide = $default_hide;
3226
	}
3227
3228
3229
	/**
3230
	 * Are some of columns hidden bydefault?
3231
	 */
3232
	public function hasSomeColumnDefaultHide()
3233
	{
3234
		return $this->some_column_default_hide;
3235
	}
3236
3237
3238
	/**
3239
	 * Simply refresh url
3240
	 * @return void
3241
	 */
3242
	public function handleRefreshState()
3243
	{
3244
		$this->findSessionValues();
3245
		$this->findDefaultFilter();
3246
		$this->findDefaultSort();
3247
		$this->findDefaultPerPage();
3248
3249
		$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...
3250
		$this->redrawControl('non-existing-snippet');
3251
	}
3252
3253
}
3254