Completed
Push — master ( 705d73...141d05 )
by Pavel
02:56
created

DataGrid::handleEdit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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