Completed
Push — master ( 818768...23fe37 )
by Pavel
03:28
created

DataGrid::redrawItem()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

Loading history...
1070
	{
1071
		$column = $column ?: $key;
1072
1073
		if (!is_string($column)) {
1074
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1075
		}
1076
1077
		$this->addFilterCheck($key);
1078
1079
		return $this->filters[$key] = new Filter\FilterMultiSelect($this, $key, $name, $options, $column);
1080
	}
1081
1082
1083
	/**
1084
	 * Add datepicker filter
1085
	 * @param string $key
1086
	 * @param string $name
1087
	 * @param string $column
1088
	 * @return Filter\FilterDate
1089
	 * @throws DataGridException
1090
	 */
1091 View Code Duplication
	public function addFilterDate($key, $name, $column = NULL)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1092
	{
1093
		$column = $column ?: $key;
1094
1095
		if (!is_string($column)) {
1096
			throw new DataGridException("FilterDate can only filter in one column.");
1097
		}
1098
1099
		$this->addFilterCheck($key);
1100
1101
		return $this->filters[$key] = new Filter\FilterDate($this, $key, $name, $column);
1102
	}
1103
1104
1105
	/**
1106
	 * Add range filter (from - to)
1107
	 * @param string $key
1108
	 * @param string $name
1109
	 * @param string $column
1110
	 * @return Filter\FilterRange
1111
	 * @throws DataGridException
1112
	 */
1113 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...
1114
	{
1115
		$column = $column ?: $key;
1116
1117
		if (!is_string($column)) {
1118
			throw new DataGridException("FilterRange can only filter in one column.");
1119
		}
1120
1121
		$this->addFilterCheck($key);
1122
1123
		return $this->filters[$key] = new Filter\FilterRange($this, $key, $name, $column, $name_second);
1124
	}
1125
1126
1127
	/**
1128
	 * Add datepicker filter (from - to)
1129
	 * @param string $key
1130
	 * @param string $name
1131
	 * @param string $column
1132
	 * @return Filter\FilterDateRange
1133
	 * @throws DataGridException
1134
	 */
1135 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...
1136
	{
1137
		$column = $column ?: $key;
1138
1139
		if (!is_string($column)) {
1140
			throw new DataGridException("FilterDateRange can only filter in one column.");
1141
		}
1142
1143
		$this->addFilterCheck($key);
1144
1145
		return $this->filters[$key] = new Filter\FilterDateRange($this, $key, $name, $column, $name_second);
1146
	}
1147
1148
1149
	/**
1150
	 * Check whether given key already exists in $this->filters
1151
	 * @param  string $key
1152
	 * @throws DataGridException
1153
	 */
1154
	protected function addFilterCheck($key)
1155
	{
1156
		if (isset($this->filters[$key])) {
1157
			throw new DataGridException("There is already action at key [$key] defined.");
1158
		}
1159
	}
1160
1161
1162
	/**
1163
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1164
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1165
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1166
	 */
1167
	public function assableFilters()
1168
	{
1169
		foreach ($this->filter as $key => $value) {
1170
			if (!isset($this->filters[$key])) {
1171
				$this->deleteSesssionData($key);
1172
1173
				continue;
1174
			}
1175
1176
			if (is_array($value) || $value instanceof \Traversable) {
1177
				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...
1178
					$this->filters[$key]->setValue($value);
1179
				}
1180
			} else {
1181
				if ($value !== '' && $value !== NULL) {
1182
					$this->filters[$key]->setValue($value);
1183
				}
1184
			}
1185
		}
1186
1187
		foreach ($this->columns as $key => $column) {
1188
			if (isset($this->sort[$key])) {
1189
				$column->setSort($this->sort);
1190
			}
1191
		}
1192
1193
		return $this->filters;
1194
	}
1195
1196
1197
	/**
1198
	 * Remove filter
1199
	 * @param string $key
1200
	 * @return void
1201
	 */
1202
	public function removeFilter($key)
1203
	{
1204
		unset($this->filters[$key]);
1205
	}
1206
1207
1208
	/**
1209
	 * Get defined filter
1210
	 * @param  string $key
1211
	 * @return Filter\Filter
1212
	 */
1213
	public function getFilter($key)
1214
	{
1215
		if (!isset($this->filters[$key])) {
1216
			throw new DataGridException("Filter [{$key}] is not defined");
1217
		}
1218
1219
		return $this->filters[$key];
1220
	}
1221
1222
1223
	/********************************************************************************
1224
	 *                                  FILTERING                                   *
1225
	 ********************************************************************************/
1226
1227
1228
	/**
1229
	 * Is filter active?
1230
	 * @return boolean
1231
	 */
1232
	public function isFilterActive()
1233
	{
1234
		$is_filter = ArraysHelper::testTruthy($this->filter);
1235
1236
		return ($is_filter) || $this->force_filter_active;
1237
	}
1238
1239
1240
	/**
1241
	 * Tell that filter is active from whatever reasons
1242
	 * return static
1243
	 */
1244
	public function setFilterActive()
1245
	{
1246
		$this->force_filter_active = TRUE;
1247
1248
		return $this;
1249
	}
1250
1251
1252
	/**
1253
	 * Set filter values (force - overwrite user data)
1254
	 * @param array $filter
1255
	 * @return static
1256
	 */
1257
	public function setFilter(array $filter)
1258
	{
1259
		$this->filter = $filter;
1260
1261
		$this->saveSessionData('_grid_has_filtered', 1);
1262
1263
		return $this;
1264
	}
1265
1266
1267
	/**
1268
	 * If we want to sent some initial filter
1269
	 * @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...
1270
	 * @param bool  $use_on_reset
1271
	 * @return static
1272
	 */
1273
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1274
	{
1275
		foreach ($default_filter as $key => $value) {
1276
			$filter = $this->getFilter($key);
1277
1278
			if (!$filter) {
1279
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1280
			}
1281
1282
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1283
				throw new DataGridException(
1284
					"Default value of filter [$key] - MultiSelect has to be an array"
1285
				);
1286
			}
1287
1288
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1289
				if (!is_array($value)) {
1290
					throw new DataGridException(
1291
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1292
					);
1293
				}
1294
1295
				$temp = $value;
1296
				unset($temp['from'], $temp['to']);
1297
1298
				if (!empty($temp)) {
1299
					throw new DataGridException(
1300
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1301
					);
1302
				}
1303
			}
1304
		}
1305
1306
		$this->default_filter = $default_filter;
1307
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1308
1309
		return $this;
1310
	}
1311
1312
1313
	/**
1314
	 * User may set default filter, find it
1315
	 * @return void
1316
	 */
1317
	public function findDefaultFilter()
1318
	{
1319
		if (!empty($this->filter)) {
1320
			return;
1321
		}
1322
1323
		if ($this->getSessionData('_grid_has_filtered')) {
1324
			return;
1325
		}
1326
1327
		if (!empty($this->default_filter)) {
1328
			$this->filter = $this->default_filter;
1329
		}
1330
1331
		foreach ($this->filter as $key => $value) {
1332
			$this->saveSessionData($key, $value);
1333
		}
1334
	}
1335
1336
1337
	/**
1338
	 * FilterAndGroupAction form factory
1339
	 * @return Form
1340
	 */
1341
	public function createComponentFilter()
1342
	{
1343
		$form = new Form($this, 'filter');
1344
1345
		$form->setMethod(static::$form_method);
1346
1347
		$form->setTranslator($this->getTranslator());
1348
1349
		/**
1350
		 * InlineEdit part
1351
		 */
1352
		$inline_edit_container = $form->addContainer('inline_edit');
1353
1354 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...
1355
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1356
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1357
				->setValidationScope(FALSE);
1358
1359
			$this->inlineEdit->onControlAdd($inline_edit_container);
1360
			$this->inlineEdit->onControlAfterAdd($inline_edit_container);
1361
		}
1362
1363
		/**
1364
		 * InlineAdd part
1365
		 */
1366
		$inline_add_container = $form->addContainer('inline_add');
1367
1368 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...
1369
			$inline_add_container->addSubmit('submit', 'ublaboo_datagrid.save');
1370
			$inline_add_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1371
				->setValidationScope(FALSE)
1372
				->setAttribute('data-datagrid-cancel-inline-add', TRUE);
1373
1374
			$this->inlineAdd->onControlAdd($inline_add_container);
1375
			$this->inlineAdd->onControlAfterAdd($inline_add_container);
1376
		}
1377
1378
		/**
1379
		 * ItemDetail form part
1380
		 */
1381
		$items_detail_form = $this->getItemDetailForm();
1382
1383
		if ($items_detail_form instanceof Nette\Forms\Container) {
1384
			$form['items_detail_form'] = $items_detail_form;
1385
		}
1386
1387
		/**
1388
		 * Filter part
1389
		 */
1390
		$filter_container = $form->addContainer('filter');
1391
1392
		foreach ($this->filters as $filter) {
1393
			$filter->addToFormContainer($filter_container);
1394
		}
1395
1396
		if (!$this->hasAutoSubmit()) {
1397
			$filter_container['submit'] = $this->getFilterSubmitButton();
1398
		}
1399
1400
		/**
1401
		 * Group action part
1402
		 */
1403
		$group_action_container = $form->addContainer('group_action');
1404
1405
		if ($this->hasGroupActions()) {
1406
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1407
		}
1408
1409
		$form->setDefaults(['filter' => $this->filter]);
1410
1411
		/**
1412
		 * Per page part
1413
		 */
1414
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1415
			->setTranslator(NULL);
1416
1417
		if (!$form->isSubmitted()) {
1418
			$form['per_page']->setValue($this->getPerPage());
1419
		}
1420
1421
		$form->addSubmit('per_page_submit', '');
1422
		
1423
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1424
1425
		return $form;
1426
	}
1427
1428
1429
	/**
1430
	 * Set $this->filter values after filter form submitted
1431
	 * @param  Form $form
1432
	 * @return void
1433
	 */
1434
	public function filterSucceeded(Form $form)
1435
	{
1436
		if ($this->snippets_set) {
1437
			return;
1438
		}
1439
1440
		$values = $form->getValues();
1441
1442
		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...
1443
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1444
				return;
1445
			}
1446
		}
1447
1448
		/**
1449
		 * Per page
1450
		 */
1451
		$this->saveSessionData('_grid_per_page', $values->per_page);
1452
		$this->per_page = $values->per_page;
1453
1454
		/**
1455
		 * Inline edit
1456
		 */
1457
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1458
			$edit = $form['inline_edit'];
1459
1460
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1461
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1462
				$primary_where_column = $form->getHttpData(
1463
					Form::DATA_LINE,
1464
					'inline_edit[_primary_where_column]'
1465
				);
1466
1467
				if ($edit['submit']->isSubmittedBy()) {
1468
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1469
					$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...
1470
				} else {
1471
					$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...
1472
				}
1473
1474
				if ($edit['submit']->isSubmittedBy() && !empty($this->inlineEdit->onCustomRedraw)) {
1475
					$this->inlineEdit->onCustomRedraw();
0 ignored issues
show
Documentation Bug introduced by
The method onCustomRedraw does not exist on object<Ublaboo\DataGrid\InlineEdit\InlineEdit>? Since you implemented __call, maybe consider adding a @method annotation.

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

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

class ParentClass {
    private $data = array();

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

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

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
1476
				} else {
1477
					$this->redrawItem($id, $primary_where_column);
1478
				}
1479
1480
				return;
1481
			}
1482
		}
1483
1484
		/**
1485
		 * Inline add
1486
		 */
1487
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1488
			$add = $form['inline_add'];
1489
1490
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1491
				if ($add['submit']->isSubmittedBy()) {
1492
					$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...
1493
1494
					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...
1495
						$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...
1496
					}
1497
				}
1498
1499
				return;
1500
			}
1501
		}
1502
1503
		/**
1504
		 * Filter itself
1505
		 */
1506
		$values = $values['filter'];
1507
1508
		foreach ($values as $key => $value) {
1509
			/**
1510
			 * Session stuff
1511
			 */
1512
			$this->saveSessionData($key, $value);
1513
1514
			/**
1515
			 * Other stuff
1516
			 */
1517
			$this->filter[$key] = $value;
1518
		}
1519
1520
		if (!empty($values)) {
1521
			$this->saveSessionData('_grid_has_filtered', 1);
1522
		}
1523
1524
		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...
1525
			$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...
1526
1527
			foreach ($this->columns as $key => $column) {
1528
				if ($column->isSortable()) {
1529
					$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...
1530
						'sort' => $column->getSortNext()
1531
					]);
1532
				}
1533
			}
1534
		}
1535
1536
		$this->reload();
1537
	}
1538
1539
1540
	/**
1541
	 * Should be datagrid filters rendered separately?
1542
	 * @param boolean $out
1543
	 * @return static
1544
	 */
1545
	public function setOuterFilterRendering($out = TRUE)
1546
	{
1547
		$this->outer_filter_rendering = (bool) $out;
1548
1549
		return $this;
1550
	}
1551
1552
1553
	/**
1554
	 * Are datagrid filters rendered separately?
1555
	 * @return boolean
1556
	 */
1557
	public function hasOuterFilterRendering()
1558
	{
1559
		return $this->outer_filter_rendering;
1560
	}
1561
1562
1563
	/**
1564
	 * Try to restore session stuff
1565
	 * @return void
1566
	 */
1567
	public function findSessionValues()
1568
	{
1569
		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...
1570
			return;
1571
		}
1572
1573
		if (!$this->remember_state) {
1574
			return;
1575
		}
1576
1577
		if ($page = $this->getSessionData('_grid_page')) {
1578
			$this->page = $page;
1579
		}
1580
1581
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1582
			$this->per_page = $per_page;
1583
		}
1584
1585
		if ($sort = $this->getSessionData('_grid_sort')) {
1586
			$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...
1587
		}
1588
1589
		foreach ($this->getSessionData() as $key => $value) {
1590
			$other_session_keys = [
1591
				'_grid_per_page',
1592
				'_grid_sort',
1593
				'_grid_page',
1594
				'_grid_has_filtered',
1595
				'_grid_hidden_columns',
1596
				'_grid_hidden_columns_manipulated'
1597
			];
1598
1599
			if (!in_array($key, $other_session_keys)) {
1600
				$this->filter[$key] = $value;
1601
			}
1602
		}
1603
1604
		/**
1605
		 * When column is sorted via custom callback, apply it
1606
		 */
1607
		if (empty($this->sort_callback) && !empty($this->sort)) {
1608
			foreach ($this->sort as $key => $order) {
1609
				try {
1610
					$column = $this->getColumn($key);
1611
1612
				} catch (DataGridException $e) {
1613
					$this->deleteSesssionData('_grid_sort');
1614
					$this->sort = [];
1615
1616
					return;
1617
				}
1618
1619
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1620
					$this->sort_callback = $column->getSortableCallback();
1621
				}
1622
			}
1623
		}
1624
	}
1625
1626
1627
	/********************************************************************************
1628
	 *                                    EXPORTS                                   *
1629
	 ********************************************************************************/
1630
1631
1632
	/**
1633
	 * Add export of type callback
1634
	 * @param string $text
1635
	 * @param callable $callback
1636
	 * @param boolean $filtered
1637
	 * @return Export\Export
1638
	 */
1639
	public function addExportCallback($text, $callback, $filtered = FALSE)
1640
	{
1641
		if (!is_callable($callback)) {
1642
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1643
		}
1644
1645
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1646
	}
1647
1648
1649
	/**
1650
	 * Add already implemented csv export
1651
	 * @param string $text
1652
	 * @param string $csv_file_name
1653
	 * @param string|null $output_encoding
1654
	 * @param string|null $delimiter
1655
	 * @param bool $include_bom
1656
	 * @return Export\Export
1657
	 */
1658 View Code Duplication
	public function addExportCsv($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1659
	{
1660
		return $this->addToExports(new Export\ExportCsv(
1661
			$this,
1662
			$text,
1663
			$csv_file_name,
1664
			FALSE,
1665
			$output_encoding,
1666
			$delimiter,
1667
			$include_bom
1668
		));
1669
	}
1670
1671
1672
	/**
1673
	 * Add already implemented csv export, but for filtered data
1674
	 * @param string $text
1675
	 * @param string $csv_file_name
1676
	 * @param string|null $output_encoding
1677
	 * @param string|null $delimiter
1678
	 * @param bool $include_bom
1679
	 * @return Export\Export
1680
	 */
1681 View Code Duplication
	public function addExportCsvFiltered($text, $csv_file_name, $output_encoding = NULL, $delimiter = NULL, $include_bom = FALSE)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1682
	{
1683
		return $this->addToExports(new Export\ExportCsv(
1684
			$this,
1685
			$text,
1686
			$csv_file_name,
1687
			TRUE,
1688
			$output_encoding,
1689
			$delimiter,
1690
			$include_bom
1691
		));
1692
	}
1693
1694
1695
	/**
1696
	 * Add export to array
1697
	 * @param Export\Export $export
1698
	 * @return Export\Export
1699
	 */
1700
	protected function addToExports(Export\Export $export)
1701
	{
1702
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1703
1704
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1705
1706
		return $this->exports[$id] = $export;
1707
	}
1708
1709
1710
	public function resetExportsLinks()
1711
	{
1712
		foreach ($this->exports as $id => $export) {
1713
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1714
		}
1715
	}
1716
1717
1718
	/********************************************************************************
1719
	 *                                TOOLBAR BUTTONS                               *
1720
	 ********************************************************************************/
1721
1722
1723
	/**
1724
	 * Add toolbar button
1725
	 * @param string $href
1726
	 * @param string $text
1727
	 * @param array  $params
1728
	 * @return ToolbarButton
1729
	 */
1730
	public function addToolbarButton($href, $text = '', $params = [])
1731
	{
1732
		$button = new ToolbarButton($this, $href, $text, $params);
1733
1734
		return $this->toolbar_buttons[] = $button;
1735
	}
1736
1737
1738
	/********************************************************************************
1739
	 *                                 GROUP ACTIONS                                *
1740
	 ********************************************************************************/
1741
1742
1743
	/**
1744
	 * Alias for add group select action
1745
	 * @param string $title
1746
	 * @param array  $options
1747
	 * @return GroupAction\GroupAction
1748
	 */
1749
	public function addGroupAction($title, $options = [])
1750
	{
1751
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1752
	}
1753
1754
1755
	/**
1756
	 * Add group action (select box)
1757
	 * @param string $title
1758
	 * @param array  $options
1759
	 * @return GroupAction\GroupAction
1760
	 */
1761
	public function addGroupSelectAction($title, $options = [])
1762
	{
1763
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1764
	}
1765
1766
1767
	/**
1768
	 * Add group action (text input)
1769
	 * @param string $title
1770
	 * @return GroupAction\GroupAction
1771
	 */
1772
	public function addGroupTextAction($title)
1773
	{
1774
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1775
	}
1776
1777
1778
	/**
1779
	 * Add group action (textarea)
1780
	 * @param string $title
1781
	 * @return GroupAction\GroupAction
1782
	 */
1783
	public function addGroupTextareaAction($title)
1784
	{
1785
		return $this->getGroupActionCollection()->addGroupTextareaAction($title);
1786
	}
1787
1788
1789
	/**
1790
	 * Get collection of all group actions
1791
	 * @return GroupAction\GroupActionCollection
1792
	 */
1793
	public function getGroupActionCollection()
1794
	{
1795
		if (!$this->group_action_collection) {
1796
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1797
		}
1798
1799
		return $this->group_action_collection;
1800
	}
1801
1802
1803
	/**
1804
	 * Has datagrid some group actions?
1805
	 * @return boolean
1806
	 */
1807
	public function hasGroupActions()
1808
	{
1809
		return (bool) $this->group_action_collection;
1810
	}
1811
1812
1813
	/********************************************************************************
1814
	 *                                   HANDLERS                                   *
1815
	 ********************************************************************************/
1816
1817
1818
	/**
1819
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1820
	 * @param  int  $page
1821
	 * @return void
1822
	 */
1823
	public function handlePage($page)
1824
	{
1825
		/**
1826
		 * Session stuff
1827
		 */
1828
		$this->page = $page;
1829
		$this->saveSessionData('_grid_page', $page);
1830
1831
		$this->reload(['table']);
1832
	}
1833
1834
1835
	/**
1836
	 * Handler for sorting
1837
	 * @param array $sort
1838
	 * @return void
1839
	 */
1840
	public function handleSort(array $sort)
1841
	{
1842
		$new_sort = [];
1843
1844
		/**
1845
		 * Find apropirate column
1846
		 */
1847
		foreach ($sort as $key => $value) {
1848
			if (empty($this->columns[$key])) {
1849
				throw new DataGridException("Column <$key> not found");
1850
			}
1851
1852
			$column = $this->columns[$key];
1853
			$new_sort = [$key => $value];
1854
1855
			/**
1856
			 * Pagination may be reseted after sorting
1857
			 */
1858
			if ($column->sortableResetPagination()) {
1859
				$this->page = 1;
1860
				$this->saveSessionData('_grid_page', 1);
1861
			}
1862
1863
			/**
1864
			 * Custom sorting callback may be applied
1865
			 */
1866
			if ($column->getSortableCallback()) {
1867
				$this->sort_callback = $column->getSortableCallback();
1868
			}
1869
		}
1870
1871
		/**
1872
		 * Session stuff
1873
		 */
1874
		$this->sort = $new_sort;
1875
		$this->saveSessionData('_grid_sort', $this->sort);
1876
1877
		$this->reload(['table']);
1878
	}
1879
1880
1881
	/**
1882
	 * handler for reseting the filter
1883
	 * @return void
1884
	 */
1885
	public function handleResetFilter()
1886
	{
1887
		/**
1888
		 * Session stuff
1889
		 */
1890
		$this->deleteSesssionData('_grid_page');
1891
1892
		if ($this->default_filter_use_on_reset) {
1893
			$this->deleteSesssionData('_grid_has_filtered');
1894
		}
1895
1896
		foreach ($this->getSessionData() as $key => $value) {
1897
			if (!in_array($key, [
1898
				'_grid_per_page',
1899
				'_grid_sort',
1900
				'_grid_page',
1901
				'_grid_has_filtered',
1902
				'_grid_hidden_columns',
1903
				'_grid_hidden_columns_manipulated'
1904
				])) {
1905
1906
				$this->deleteSesssionData($key);
1907
			}
1908
		}
1909
1910
		$this->filter = [];
1911
1912
		$this->reload(['grid']);
1913
	}
1914
1915
1916
	/**
1917
	 * Handler for export
1918
	 * @param  int $id Key for particular export class in array $this->exports
1919
	 * @return void
1920
	 */
1921
	public function handleExport($id)
1922
	{
1923
		if (!isset($this->exports[$id])) {
1924
			throw new Nette\Application\ForbiddenRequestException;
1925
		}
1926
1927
		if (!empty($this->columns_export_order)) {
1928
			$this->setColumnsOrder($this->columns_export_order);
1929
		}
1930
1931
		$export = $this->exports[$id];
1932
1933
		if ($export->isFiltered()) {
1934
			$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...
1935
			$filter    = $this->assableFilters();
1936
		} else {
1937
			$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...
1938
			$filter    = [];
1939
		}
1940
1941
		if (NULL === $this->dataModel) {
1942
			throw new DataGridException('You have to set a data source first.');
1943
		}
1944
1945
		$rows = [];
1946
1947
		$items = Nette\Utils\Callback::invokeArgs(
1948
			[$this->dataModel, 'filterData'], [
1949
				NULL,
1950
				$this->createSorting($this->sort, $this->sort_callback),
1951
				$filter
1952
			]
1953
		);
1954
1955
		foreach ($items as $item) {
1956
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1957
		}
1958
1959
		if ($export instanceof Export\ExportCsv) {
1960
			$export->invoke($rows);
1961
		} else {
1962
			$export->invoke($items);
1963
		}
1964
1965
		if ($export->isAjax()) {
1966
			$this->reload();
1967
		}
1968
	}
1969
1970
1971
	/**
1972
	 * Handler for getting children of parent item (e.g. category)
1973
	 * @param  int $parent
1974
	 * @return void
1975
	 */
1976
	public function handleGetChildren($parent)
1977
	{
1978
		$this->setDataSource(
1979
			call_user_func($this->tree_view_children_callback, $parent)
1980
		);
1981
1982
		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...
1983
			$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...
1984
			$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...
1985
1986
			$this->redrawControl('items');
1987
1988
			$this->onRedraw();
1989
		} else {
1990
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Component, Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1991
		}
1992
	}
1993
1994
1995
	/**
1996
	 * Handler for getting item detail
1997
	 * @param  mixed $id
1998
	 * @return void
1999
	 */
2000
	public function handleGetItemDetail($id)
2001
	{
2002
		$this->getTemplate()->add('toggle_detail', $id);
2003
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
2004
2005
		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...
2006
			$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...
2007
			$this->redrawControl('items');
2008
2009
			$this->onRedraw();
2010
		} else {
2011
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Component, Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2012
		}
2013
	}
2014
2015
2016
	/**
2017
	 * Handler for inline editing
2018
	 * @param  mixed $id
2019
	 * @param  mixed $key
2020
	 * @return void
2021
	 */
2022
	public function handleEdit($id, $key)
2023
	{
2024
		$column = $this->getColumn($key);
2025
		$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...
2026
2027
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
2028
	}
2029
2030
2031
	/**
2032
	 * Redraw $this
2033
	 * @return void
2034
	 */
2035
	public function reload($snippets = [])
2036
	{
2037
		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...
2038
			$this->redrawControl('tbody');
2039
			$this->redrawControl('pagination');
2040
2041
			/**
2042
			 * manualy reset exports links...
2043
			 */
2044
			$this->resetExportsLinks();
2045
			$this->redrawControl('exports');
2046
2047
			foreach ($snippets as $snippet) {
2048
				$this->redrawControl($snippet);
2049
			}
2050
2051
			$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...
2052
2053
			$this->onRedraw();
2054
		} else {
2055
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Component, Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
2056
		}
2057
	}
2058
2059
2060
	/**
2061
	 * Handler for column status
2062
	 * @param  string $id
2063
	 * @param  string $key
2064
	 * @param  string $value
2065
	 * @return void
2066
	 */
2067
	public function handleChangeStatus($id, $key, $value)
2068
	{
2069
		if (empty($this->columns[$key])) {
2070
			throw new DataGridException("ColumnStatus[$key] does not exist");
2071
		}
2072
2073
		$this->columns[$key]->onChange($id, $value);
2074
	}
2075
2076
2077
	/**
2078
	 * Redraw just one row via ajax
2079
	 * @param  int   $id
2080
	 * @param  mixed $primary_where_column
2081
	 * @return void
2082
	 */
2083
	public function redrawItem($id, $primary_where_column = NULL)
2084
	{
2085
		$this->snippets_set = TRUE;
2086
2087
		$this->redraw_item = [($primary_where_column ?: $this->primary_key) => $id];
2088
2089
		$this->redrawControl('items');
2090
2091
		$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...
2092
2093
		$this->onRedraw();
2094
	}
2095
2096
2097
	/**
2098
	 * Tell datagrid to display all columns
2099
	 * @return void
2100
	 */
2101
	public function handleShowAllColumns()
2102
	{
2103
		$this->deleteSesssionData('_grid_hidden_columns');
2104
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2105
2106
		$this->redrawControl();
2107
2108
		$this->onRedraw();
2109
	}
2110
2111
2112
	/**
2113
	 * Tell datagrid to display default columns
2114
	 * @return void
2115
	 */
2116
	public function handleShowDefaultColumns()
2117
	{
2118
		$this->deleteSesssionData('_grid_hidden_columns');
2119
		$this->saveSessionData('_grid_hidden_columns_manipulated', FALSE);
2120
2121
		$this->redrawControl();
2122
2123
		$this->onRedraw();
2124
	}
2125
2126
2127
	/**
2128
	 * Reveal particular column
2129
	 * @param  string $column
2130
	 * @return void
2131
	 */
2132 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...
2133
	{
2134
		$columns = $this->getSessionData('_grid_hidden_columns');
2135
2136
		if (!empty($columns)) {
2137
			$pos = array_search($column, $columns);
2138
2139
			if ($pos !== FALSE) {
2140
				unset($columns[$pos]);
2141
			}
2142
		}
2143
2144
		$this->saveSessionData('_grid_hidden_columns', $columns);
2145
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2146
2147
		$this->redrawControl();
2148
2149
		$this->onRedraw();
2150
	}
2151
2152
2153
	/**
2154
	 * Notice datagrid to not display particular columns
2155
	 * @param  string $column
2156
	 * @return void
2157
	 */
2158 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...
2159
	{
2160
		/**
2161
		 * Store info about hiding a column to session
2162
		 */
2163
		$columns = $this->getSessionData('_grid_hidden_columns');
2164
2165
		if (empty($columns)) {
2166
			$columns = [$column];
2167
		} else if (!in_array($column, $columns)) {
2168
			array_push($columns, $column);
2169
		}
2170
2171
		$this->saveSessionData('_grid_hidden_columns', $columns);
2172
		$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2173
2174
		$this->redrawControl();
2175
2176
		$this->onRedraw();
2177
	}
2178
2179
2180
	public function handleActionCallback($__key, $__id)
2181
	{
2182
		$action = $this->getAction($__key);
2183
2184
		if (!($action instanceof Column\ActionCallback)) {
2185
			throw new DataGridException("Action [$__key] does not exist or is not an callback aciton.");
2186
		}
2187
2188
		$action->onClick($__id);
2189
	}
2190
2191
2192
	/********************************************************************************
2193
	 *                                  PAGINATION                                  *
2194
	 ********************************************************************************/
2195
2196
2197
	/**
2198
	 * Set options of select "items_per_page"
2199
	 * @param array $items_per_page_list
2200
	 * @return static
2201
	 */
2202
	public function setItemsPerPageList(array $items_per_page_list, $include_all = TRUE)
2203
	{
2204
		$this->items_per_page_list = $items_per_page_list;
2205
2206
		if ($include_all) {
2207
			$this->items_per_page_list[] = 'all';
2208
		}
2209
2210
		return $this;
2211
	}
2212
2213
2214
	/**
2215
	 * Set default "items per page" value in pagination select
2216
	 * @param $count
2217
	 * @return static
2218
	 */
2219
	public function setDefaultPerPage($count)
2220
	{
2221
		$this->default_per_page = $count;
2222
2223
		return $this;
2224
	}
2225
2226
2227
	/**
2228
	 * User may set default "items per page" value, apply it
2229
	 * @return void
2230
	 */
2231
	public function findDefaultPerPage()
2232
	{
2233
		if (!empty($this->per_page)) {
2234
			return;
2235
		}
2236
2237
		if (!empty($this->default_per_page)) {
2238
			$this->per_page = $this->default_per_page;
2239
		}
2240
2241
		$this->saveSessionData('_grid_per_page', $this->per_page);
2242
	}
2243
2244
2245
	/**
2246
	 * Paginator factory
2247
	 * @return Components\DataGridPaginator\DataGridPaginator
2248
	 */
2249
	public function createComponentPaginator()
2250
	{
2251
		/**
2252
		 * Init paginator
2253
		 */
2254
		$component = new Components\DataGridPaginator\DataGridPaginator(
2255
			$this->getTranslator(),
2256
			static::$icon_prefix
2257
		);
2258
		$paginator = $component->getPaginator();
2259
2260
		$paginator->setPage($this->page);
2261
		$paginator->setItemsPerPage($this->getPerPage());
2262
2263
		return $component;
2264
	}
2265
2266
2267
	/**
2268
	 * Get parameter per_page
2269
	 * @return int
2270
	 */
2271
	public function getPerPage()
2272
	{
2273
		$items_per_page_list = $this->getItemsPerPageList();
2274
2275
		$per_page = $this->per_page ?: reset($items_per_page_list);
2276
2277
		if ($per_page !== 'all' && !in_array($this->per_page, $items_per_page_list)) {
2278
			$per_page = reset($items_per_page_list);
2279
		}
2280
2281
		return $per_page;
2282
	}
2283
2284
2285
	/**
2286
	 * Get associative array of items_per_page_list
2287
	 * @return array
2288
	 */
2289
	public function getItemsPerPageList()
2290
	{
2291
		if (empty($this->items_per_page_list)) {
2292
			$this->setItemsPerPageList([10, 20, 50], TRUE);
2293
		}
2294
2295
		$list = array_flip($this->items_per_page_list);
2296
2297
		foreach ($list as $key => $value) {
2298
			$list[$key] = $key;
2299
		}
2300
2301
		if (array_key_exists('all', $list)) {
2302
			$list['all'] = $this->getTranslator()->translate('ublaboo_datagrid.all');
2303
		}
2304
2305
		return $list;
2306
	}
2307
2308
2309
	/**
2310
	 * Order Grid to "be paginated"
2311
	 * @param bool $do
2312
	 * @return static
2313
	 */
2314
	public function setPagination($do)
2315
	{
2316
		$this->do_paginate = (bool) $do;
2317
2318
		return $this;
2319
	}
2320
2321
2322
	/**
2323
	 * Tell whether Grid is paginated
2324
	 * @return bool
2325
	 */
2326
	public function isPaginated()
2327
	{
2328
		return $this->do_paginate;
2329
	}
2330
2331
2332
	/**
2333
	 * Return current paginator class
2334
	 * @return NULL|Components\DataGridPaginator\DataGridPaginator
2335
	 */
2336
	public function getPaginator()
2337
	{
2338
		if ($this->isPaginated() && $this->getPerPage() !== 'all') {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $this->getPerPage() (integer) and 'all' (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
2339
			return $this['paginator'];
2340
		}
2341
2342
		return NULL;
2343
	}
2344
2345
2346
	/********************************************************************************
2347
	 *                                     I18N                                     *
2348
	 ********************************************************************************/
2349
2350
2351
	/**
2352
	 * Set datagrid translator
2353
	 * @param Nette\Localization\ITranslator $translator
2354
	 * @return static
2355
	 */
2356
	public function setTranslator(Nette\Localization\ITranslator $translator)
2357
	{
2358
		$this->translator = $translator;
2359
2360
		return $this;
2361
	}
2362
2363
2364
	/**
2365
	 * Get translator for datagrid
2366
	 * @return Nette\Localization\ITranslator
2367
	 */
2368
	public function getTranslator()
2369
	{
2370
		if (!$this->translator) {
2371
			$this->translator = new Localization\SimpleTranslator;
2372
		}
2373
2374
		return $this->translator;
2375
	}
2376
2377
2378
	/********************************************************************************
2379
	 *                                 COLUMNS ORDER                                *
2380
	 ********************************************************************************/
2381
2382
2383
	/**
2384
	 * Set order of datagrid columns
2385
	 * @param array $order
2386
	 * @return static
2387
	 */
2388
	public function setColumnsOrder($order)
2389
	{
2390
		$new_order = [];
2391
2392
		foreach ($order as $key) {
2393
			if (isset($this->columns[$key])) {
2394
				$new_order[$key] = $this->columns[$key];
2395
			}
2396
		}
2397
2398
		if (sizeof($new_order) === sizeof($this->columns)) {
2399
			$this->columns = $new_order;
2400
		} else {
2401
			throw new DataGridException('When changing columns order, you have to specify all columns');
2402
		}
2403
2404
		return $this;
2405
	}
2406
2407
2408
	/**
2409
	 * Columns order may be different for export and normal grid
2410
	 * @param array $order
2411
	 */
2412
	public function setColumnsExportOrder($order)
2413
	{
2414
		$this->columns_export_order = (array) $order;
2415
	}
2416
2417
2418
	/********************************************************************************
2419
	 *                                SESSION & URL                                 *
2420
	 ********************************************************************************/
2421
2422
2423
	/**
2424
	 * Find some unique session key name
2425
	 * @return string
2426
	 */
2427
	public function getSessionSectionName()
2428
	{
2429
		return $this->getPresenter()->getName().':'.$this->getUniqueId();
2430
	}
2431
2432
2433
	/**
2434
	 * Should datagrid remember its filters/pagination/etc using session?
2435
	 * @param bool $remember
2436
	 * @return static
2437
	 */
2438
	public function setRememberState($remember = TRUE)
2439
	{
2440
		$this->remember_state = (bool) $remember;
2441
2442
		return $this;
2443
	}
2444
2445
2446
	/**
2447
	 * Should datagrid refresh url using history API?
2448
	 * @param bool $refresh
2449
	 * @return static
2450
	 */
2451
	public function setRefreshUrl($refresh = TRUE)
2452
	{
2453
		$this->refresh_url = (bool) $refresh;
2454
2455
2456
		return $this;
2457
	}
2458
2459
2460
	/**
2461
	 * Get session data if functionality is enabled
2462
	 * @param  string $key
2463
	 * @return mixed
2464
	 */
2465
	public function getSessionData($key = NULL, $default_value = NULL)
2466
	{
2467
		if (!$this->remember_state) {
2468
			return $key ? $default_value : [];
2469
		}
2470
2471
		return ($key ? $this->grid_session->{$key} : $this->grid_session) ?: $default_value;
2472
	}
2473
2474
2475
	/**
2476
	 * Save session data - just if it is enabled
2477
	 * @param  string $key
2478
	 * @param  mixed  $value
2479
	 * @return void
2480
	 */
2481
	public function saveSessionData($key, $value)
2482
	{
2483
		if ($this->remember_state) {
2484
			$this->grid_session->{$key} = $value;
2485
		}
2486
	}
2487
2488
2489
	/**
2490
	 * Delete session data
2491
	 * @return void
2492
	 */
2493
	public function deleteSesssionData($key)
2494
	{
2495
		unset($this->grid_session->{$key});
2496
	}
2497
2498
2499
	/********************************************************************************
2500
	 *                                  ITEM DETAIL                                 *
2501
	 ********************************************************************************/
2502
2503
2504
	/**
2505
	 * Get items detail parameters
2506
	 * @return array
2507
	 */
2508
	public function getItemsDetail()
2509
	{
2510
		return $this->items_detail;
2511
	}
2512
2513
2514
	/**
2515
	 * Items can have thair detail - toggled
2516
	 * @param mixed $detail callable|string|bool
2517
	 * @param bool|NULL $primary_where_column
2518
	 * @return Column\ItemDetail
2519
	 */
2520
	public function setItemsDetail($detail = TRUE, $primary_where_column = NULL)
2521
	{
2522
		if ($this->isSortable()) {
2523
			throw new DataGridException('You can not use both sortable datagrid and items detail.');
2524
		}
2525
2526
		$this->items_detail = new Column\ItemDetail(
2527
			$this,
2528
			$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...
2529
		);
2530
2531
		if (is_string($detail)) {
2532
			/**
2533
			 * Item detail will be in separate template
2534
			 */
2535
			$this->items_detail->setType('template');
2536
			$this->items_detail->setTemplate($detail);
2537
2538
		} else if (is_callable($detail)) {
2539
			/**
2540
			 * Item detail will be rendered via custom callback renderer
2541
			 */
2542
			$this->items_detail->setType('renderer');
2543
			$this->items_detail->setRenderer($detail);
2544
2545
		} else if (TRUE === $detail) {
2546
			/**
2547
			 * Item detail will be rendered probably via block #detail
2548
			 */
2549
			$this->items_detail->setType('block');
2550
2551
		} else {
2552
			throw new DataGridException(
2553
				'::setItemsDetail() can be called either with no parameters or with parameter = template path or callable renderer.'
2554
			);
2555
		}
2556
2557
		return $this->items_detail;
2558
	}
2559
2560
2561
	/**
2562
	 * @param callable $callable_set_container 
2563
	 * @return static
2564
	 */
2565
	public function setItemsDetailForm(callable $callable_set_container)
2566
	{
2567
		if ($this->items_detail instanceof Column\ItemDetail) {
2568
			$this->items_detail->setForm(
2569
				new Utils\ItemDetailForm($callable_set_container)
2570
			);
2571
2572
			return $this;
2573
		}
2574
2575
		throw new DataGridException('Please set the ItemDetail first.');
2576
	}
2577
2578
2579
	/**
2580
	 * @return Nette\Forms\Container|NULL
2581
	 */
2582
	public function getItemDetailForm()
2583
	{
2584
		if ($this->items_detail instanceof Column\ItemDetail) {
2585
			return $this->items_detail->getForm();
2586
		}
2587
2588
		return NULL;
2589
	}
2590
2591
2592
	/********************************************************************************
2593
	 *                                ROW PRIVILEGES                                *
2594
	 ********************************************************************************/
2595
2596
2597
	/**
2598
	 * @param  callable $condition
2599
	 * @return void
2600
	 */
2601
	public function allowRowsGroupAction(callable $condition)
2602
	{
2603
		$this->row_conditions['group_action'] = $condition;
2604
	}
2605
2606
2607
	/**
2608
	 * @param  callable $condition
2609
	 * @return void
2610
	 */
2611
	public function allowRowsInlineEdit(callable $condition)
2612
	{
2613
		$this->row_conditions['inline_edit'] = $condition;
2614
	}
2615
2616
2617
	/**
2618
	 * @param  string   $key
2619
	 * @param  callable $condition
2620
	 * @return void
2621
	 */
2622
	public function allowRowsAction($key, callable $condition)
2623
	{
2624
		$this->row_conditions['action'][$key] = $condition;
2625
	}
2626
2627
2628
	/**
2629
	 * @param  string      $name
2630
	 * @param  string|null $key
2631
	 * @return bool|callable
2632
	 */
2633
	public function getRowCondition($name, $key = NULL)
2634
	{
2635
		if (!isset($this->row_conditions[$name])) {
2636
			return FALSE;
2637
		}
2638
2639
		$condition = $this->row_conditions[$name];
2640
2641
		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...
2642
			return $condition;
2643
		}
2644
2645
		return isset($condition[$key]) ? $condition[$key] : FALSE;
2646
	}
2647
2648
2649
	/********************************************************************************
2650
	 *                               COLUMN CALLBACK                                *
2651
	 ********************************************************************************/
2652
2653
2654
	/**
2655
	 * @param  string   $key
2656
	 * @param  callable $callback
2657
	 * @return void
2658
	 */
2659
	public function addColumnCallback($key, callable $callback)
2660
	{
2661
		$this->column_callbacks[$key] = $callback;
2662
	}
2663
2664
2665
	/**
2666
	 * @param  string $key
2667
	 * @return callable|null
2668
	 */
2669
	public function getColumnCallback($key)
2670
	{
2671
		return empty($this->column_callbacks[$key]) ? NULL : $this->column_callbacks[$key];
2672
	}
2673
2674
2675
	/********************************************************************************
2676
	 *                                 INLINE EDIT                                  *
2677
	 ********************************************************************************/
2678
2679
2680
	/**
2681
	 * @return InlineEdit
2682
	 */
2683
	public function addInlineEdit($primary_where_column = NULL)
2684
	{
2685
		$this->inlineEdit = new InlineEdit($this, $primary_where_column ?: $this->primary_key);
2686
2687
		return $this->inlineEdit;
2688
	}
2689
2690
2691
	/**
2692
	 * @return InlineEdit|null
2693
	 */
2694
	public function getInlineEdit()
2695
	{
2696
		return $this->inlineEdit;
2697
	}
2698
2699
2700
	/**
2701
	 * @param  mixed $id
2702
	 * @return void
2703
	 */
2704
	public function handleInlineEdit($id)
2705
	{
2706
		if ($this->inlineEdit) {
2707
			$this->inlineEdit->setItemId($id);
2708
2709
			$primary_where_column = $this->inlineEdit->getPrimaryWhereColumn();
2710
2711
			$this['filter']['inline_edit']->addHidden('_id', $id);
2712
			$this['filter']['inline_edit']->addHidden('_primary_where_column', $primary_where_column);
2713
2714
			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...
2715
				$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...
2716
			}
2717
2718
			$this->redrawItem($id, $primary_where_column);
2719
		}
2720
	}
2721
2722
2723
	/********************************************************************************
2724
	 *                                  INLINE ADD                                  *
2725
	 ********************************************************************************/
2726
2727
2728
	/**
2729
	 * @return InlineEdit
2730
	 */
2731
	public function addInlineAdd()
2732
	{
2733
		$this->inlineAdd = new InlineEdit($this);
2734
2735
		$this->inlineAdd
2736
			->setIcon('plus')
2737
			->setClass('btn btn-xs btn-default');
2738
2739
		return $this->inlineAdd;
2740
	}
2741
2742
2743
	/**
2744
	 * @return InlineEdit|null
2745
	 */
2746
	public function getInlineAdd()
2747
	{
2748
		return $this->inlineAdd;
2749
	}
2750
2751
2752
	/********************************************************************************
2753
	 *                               HIDEABLE COLUMNS                               *
2754
	 ********************************************************************************/
2755
2756
2757
	/**
2758
	 * Can datagrid hide colums?
2759
	 * @return boolean
2760
	 */
2761
	public function canHideColumns()
2762
	{
2763
		return (bool) $this->can_hide_columns;
2764
	}
2765
2766
2767
	/**
2768
	 * Order Grid to set columns hideable.
2769
	 * @return static
2770
	 */
2771
	public function setColumnsHideable()
2772
	{
2773
		$this->can_hide_columns = TRUE;
2774
2775
		return $this;
2776
	}
2777
2778
2779
	/********************************************************************************
2780
	 *                                COLUMNS SUMMARY                               *
2781
	 ********************************************************************************/
2782
2783
2784
	/**
2785
	 * Will datagrid show summary in the end?
2786
	 * @return bool
2787
	 */
2788
	public function hasColumnsSummary()
2789
	{
2790
		return $this->columnsSummary instanceof ColumnsSummary;
2791
	}
2792
2793
2794
	/**
2795
	 * Set columns to be summarized in the end.
2796
	 * @param  array  $columns
2797
	 * @return ColumnsSummary
2798
	 */
2799
	public function setColumnsSummary(array $columns)
2800
	{
2801
		$this->columnsSummary = new ColumnsSummary($this, $columns);
2802
2803
		return $this->columnsSummary;
2804
	}
2805
2806
2807
	/**
2808
	 * @return ColumnsSummary|NULL
2809
	 */
2810
	public function getColumnsSummary()
2811
	{
2812
		return $this->columnsSummary;
2813
	}
2814
2815
2816
	/********************************************************************************
2817
	 *                                   INTERNAL                                   *
2818
	 ********************************************************************************/
2819
2820
2821
	/**
2822
	 * Tell grid filters to by submitted automatically
2823
	 * @param bool $auto
2824
	 */
2825
	public function setAutoSubmit($auto = TRUE)
2826
	{
2827
		$this->auto_submit = (bool) $auto;
2828
2829
		return $this;
2830
	}
2831
2832
2833
	/**
2834
	 * @return bool
2835
	 */
2836
	public function hasAutoSubmit()
2837
	{
2838
		return $this->auto_submit;
2839
	}
2840
2841
2842
	/**
2843
	 * Submit button when no auto-submitting is used
2844
	 * @return Filter\SubmitButton
2845
	 */
2846
	public function getFilterSubmitButton()
2847
	{
2848
		if ($this->hasAutoSubmit()) {
2849
			throw new DataGridException(
2850
				'DataGrid has auto-submit. Turn it off before setting filter submit button.'
2851
			);
2852
		}
2853
2854
		if ($this->filter_submit_button === NULL) {
2855
			$this->filter_submit_button = new Filter\SubmitButton($this);
2856
		}
2857
2858
		return $this->filter_submit_button;
2859
	}
2860
2861
2862
	/********************************************************************************
2863
	 *                                   INTERNAL                                   *
2864
	 ********************************************************************************/
2865
2866
2867
	/**
2868
	 * Get count of columns
2869
	 * @return int
2870
	 */
2871
	public function getColumnsCount()
2872
	{
2873
		$count = sizeof($this->getColumns());
2874
2875
		if (!empty($this->actions)
2876
			|| $this->isSortable()
2877
			|| $this->getItemsDetail()
2878
			|| $this->getInlineEdit()
2879
			|| $this->getInlineAdd()) {
2880
			$count++;
2881
		}
2882
2883
		if ($this->hasGroupActions()) {
2884
			$count++;
2885
		}
2886
2887
		return $count;
2888
	}
2889
2890
2891
	/**
2892
	 * Get primary key of datagrid data source
2893
	 * @return string
2894
	 */
2895
	public function getPrimaryKey()
2896
	{
2897
		return $this->primary_key;
2898
	}
2899
2900
2901
	/**
2902
	 * Get set of set columns
2903
	 * @return Column\IColumn[]
2904
	 */
2905
	public function getColumns()
2906
	{
2907
		$return = $this->columns;
2908
2909
		if (!$this->getSessionData('_grid_hidden_columns_manipulated', FALSE)) {
2910
			$columns_to_hide = [];
2911
2912
			foreach ($this->columns as $key => $column) {
2913
				if ($column->getDefaultHide()) {
2914
					$columns_to_hide[] = $key;
2915
				}
2916
			}
2917
2918
			if (!empty($columns_to_hide)) {
2919
				$this->saveSessionData('_grid_hidden_columns', $columns_to_hide);
2920
				$this->saveSessionData('_grid_hidden_columns_manipulated', TRUE);
2921
			}
2922
		}
2923
2924
		$hidden_columns = $this->getSessionData('_grid_hidden_columns', []);
2925
2926
		foreach ($hidden_columns as $column) {
2927
			if (!empty($this->columns[$column])) {
2928
				$this->columns_visibility[$column] = [
2929
					'visible' => FALSE
2930
				];
2931
2932
				unset($return[$column]);
2933
			}
2934
		}
2935
2936
		return $return;
2937
	}
2938
2939
2940
	public function getColumnsVisibility()
2941
	{
2942
		$return = $this->columns_visibility;
2943
2944
		foreach ($this->columns_visibility as $key => $column) {
2945
			$return[$key]['column'] = $this->columns[$key];
2946
		}
2947
2948
		return $return;
2949
	}
2950
2951
2952
	/**
2953
	 * @return PresenterComponent
2954
	 */
2955
	public function getParent()
2956
	{
2957
		$parent = parent::getParent();
2958
2959
		if (!($parent instanceof PresenterComponent)) {
0 ignored issues
show
Bug introduced by
The class Nette\Application\UI\PresenterComponent does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

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

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

2. Missing use statement

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

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

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

Loading history...
2960
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2961
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2962
			);
2963
		}
2964
2965
		return $parent;
2966
	}
2967
2968
2969
	/**
2970
	 * Some of datagrid columns is hidden by default
2971
	 * @param bool $default_hide
2972
	 */
2973
	public function setSomeColumnDefaultHide($default_hide)
2974
	{
2975
		$this->some_column_default_hide = $default_hide;
2976
	}
2977
2978
2979
	/**
2980
	 * Are some of columns hidden bydefault?
2981
	 */
2982
	public function hasSomeColumnDefaultHide()
2983
	{
2984
		return $this->some_column_default_hide;
2985
	}
2986
2987
}
2988