Completed
Push — master ( 124556...d6c11a )
by Pavel
06:16
created

DataGrid::getItemsDetail()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid;
10
11
use Nette;
12
use Nette\Application\UI\Link;
13
use Nette\Application\UI\PresenterComponent;
14
use Ublaboo\DataGrid\Utils\ArraysHelper;
15
use Nette\Application\UI\Form;
16
use Ublaboo\DataGrid\Exception\DataGridException;
17
use Ublaboo\DataGrid\Exception\DataGridHasToBeAttachedToPresenterComponentException;
18
use Ublaboo\DataGrid\Utils\Sorting;
19
use Ublaboo\DataGrid\InlineEdit\InlineEdit;
20
use Ublaboo\DataGrid\ColumnsSummary;
21
use Ublaboo\DataGrid\Toolbar\ToolbarButton;
22
23
/**
24
 * @method onRedraw()
25
 * @method onRender()
26
 * @method onColumnAdd()
27
 */
28
class DataGrid extends Nette\Application\UI\Control
29
{
30
31
	/**
32
	 * @var callable[]
33
	 */
34
	public $onRedraw;
35
36
	/**
37
	 * @var callable[]
38
	 */
39
	public $onRender = [];
40
41
	/**
42
	 * @var callable[]
43
	 */
44
	public $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->columns_visibility);
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
			'name' => $column->getName()
874
		];
875
876
		return $this->columns[$key] = $column;
877
	}
878
879
880
	/**
881
	 * Return existing column
882
	 * @param  string $key
883
	 * @return Column\Column
884
	 * @throws DataGridException
885
	 */
886
	public function getColumn($key)
887
	{
888
		if (!isset($this->columns[$key])) {
889
			throw new DataGridException("There is no column at key [$key] defined.");
890
		}
891
892
		return $this->columns[$key];
893
	}
894
895
896
	/**
897
	 * Remove column
898
	 * @param string $key
899
	 * @return void
900
	 */
901
	public function removeColumn($key)
902
	{
903
		unset($this->columns[$key]);
904
	}
905
906
907
	/**
908
	 * Check whether given key already exists in $this->columns
909
	 * @param  string $key
910
	 * @throws DataGridException
911
	 */
912
	protected function addColumnCheck($key)
913
	{
914
		if (isset($this->columns[$key])) {
915
			throw new DataGridException("There is already column at key [$key] defined.");
916
		}
917
	}
918
919
920
	/********************************************************************************
921
	 *                                    ACTIONS                                   *
922
	 ********************************************************************************/
923
924
925
	/**
926
	 * Create action
927
	 * @param string     $key
928
	 * @param string     $name
929
	 * @param string     $href
930
	 * @param array|null $params
931
	 * @return Column\Action
932
	 */
933
	public function addAction($key, $name, $href = NULL, array $params = NULL)
934
	{
935
		$this->addActionCheck($key);
936
		$href = $href ?: $key;
937
938
		if (NULL === $params) {
939
			$params = [$this->primary_key];
940
		}
941
942
		return $this->actions[$key] = new Column\Action($this, $href, $name, $params);
943
	}
944
945
946
	/**
947
	 * Create action callback
948
	 * @param string     $key
949
	 * @param string     $name
950
	 * @return Column\Action
951
	 */
952
	public function addActionCallback($key, $name, $callback = NULL)
953
	{
954
		$this->addActionCheck($key);
955
		$params = ['__id' => $this->primary_key];
956
957
		$this->actions[$key] = $action = new Column\ActionCallback($this, $key, $name, $params);
958
959
		if ($callback) {
960
			if (!is_callable($callback)) {
961
				throw new DataGridException('ActionCallback callback has to be callable.');
962
			}
963
964
			$action->onClick[] = $callback;
965
		}
966
967
		return $action;
968
	}
969
970
971
	/**
972
	 * Get existing action
973
	 * @param  string       $key
974
	 * @return Column\Action
975
	 * @throws DataGridException
976
	 */
977
	public function getAction($key)
978
	{
979
		if (!isset($this->actions[$key])) {
980
			throw new DataGridException("There is no action at key [$key] defined.");
981
		}
982
983
		return $this->actions[$key];
984
	}
985
986
987
	/**
988
	 * Remove action
989
	 * @param string $key
990
	 * @return void
991
	 */
992
	public function removeAction($key)
993
	{
994
		unset($this->actions[$key]);
995
	}
996
997
998
	/**
999
	 * Check whether given key already exists in $this->filters
1000
	 * @param  string $key
1001
	 * @throws DataGridException
1002
	 */
1003
	protected function addActionCheck($key)
1004
	{
1005
		if (isset($this->actions[$key])) {
1006
			throw new DataGridException("There is already action at key [$key] defined.");
1007
		}
1008
	}
1009
1010
1011
	/********************************************************************************
1012
	 *                                    FILTERS                                   *
1013
	 ********************************************************************************/
1014
1015
1016
	/**
1017
	 * Add filter fot text search
1018
	 * @param string       $key
1019
	 * @param string       $name
1020
	 * @param array|string $columns
1021
	 * @return Filter\FilterText
1022
	 * @throws DataGridException
1023
	 */
1024
	public function addFilterText($key, $name, $columns = NULL)
1025
	{
1026
		$columns = NULL === $columns ? [$key] : (is_string($columns) ? [$columns] : $columns);
1027
1028
		if (!is_array($columns)) {
1029
			throw new DataGridException("Filter Text can except only array or string.");
1030
		}
1031
1032
		$this->addFilterCheck($key);
1033
1034
		return $this->filters[$key] = new Filter\FilterText($this, $key, $name, $columns);
1035
	}
1036
1037
1038
	/**
1039
	 * Add select box filter
1040
	 * @param string $key
1041
	 * @param string $name
1042
	 * @param array  $options
1043
	 * @param string $column
1044
	 * @return Filter\FilterSelect
1045
	 * @throws DataGridException
1046
	 */
1047 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...
1048
	{
1049
		$column = $column ?: $key;
1050
1051
		if (!is_string($column)) {
1052
			throw new DataGridException("Filter Select can only filter in one column.");
1053
		}
1054
1055
		$this->addFilterCheck($key);
1056
1057
		return $this->filters[$key] = new Filter\FilterSelect($this, $key, $name, $options, $column);
1058
	}
1059
1060
1061
	/**
1062
	 * Add multi select box filter
1063
	 * @param string $key
1064
	 * @param string $name
1065
	 * @param array  $options
1066
	 * @param string $column
1067
	 * @return Filter\FilterSelect
1068
	 * @throws DataGridException
1069
	 */
1070 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...
1071
	{
1072
		$column = $column ?: $key;
1073
1074
		if (!is_string($column)) {
1075
			throw new DataGridException("Filter MultiSelect can only filter in one column.");
1076
		}
1077
1078
		$this->addFilterCheck($key);
1079
1080
		return $this->filters[$key] = new Filter\FilterMultiSelect($this, $key, $name, $options, $column);
1081
	}
1082
1083
1084
	/**
1085
	 * Add datepicker filter
1086
	 * @param string $key
1087
	 * @param string $name
1088
	 * @param string $column
1089
	 * @return Filter\FilterDate
1090
	 * @throws DataGridException
1091
	 */
1092 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...
1093
	{
1094
		$column = $column ?: $key;
1095
1096
		if (!is_string($column)) {
1097
			throw new DataGridException("FilterDate can only filter in one column.");
1098
		}
1099
1100
		$this->addFilterCheck($key);
1101
1102
		return $this->filters[$key] = new Filter\FilterDate($this, $key, $name, $column);
1103
	}
1104
1105
1106
	/**
1107
	 * Add range filter (from - to)
1108
	 * @param string $key
1109
	 * @param string $name
1110
	 * @param string $column
1111
	 * @return Filter\FilterRange
1112
	 * @throws DataGridException
1113
	 */
1114 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...
1115
	{
1116
		$column = $column ?: $key;
1117
1118
		if (!is_string($column)) {
1119
			throw new DataGridException("FilterRange can only filter in one column.");
1120
		}
1121
1122
		$this->addFilterCheck($key);
1123
1124
		return $this->filters[$key] = new Filter\FilterRange($this, $key, $name, $column, $name_second);
1125
	}
1126
1127
1128
	/**
1129
	 * Add datepicker filter (from - to)
1130
	 * @param string $key
1131
	 * @param string $name
1132
	 * @param string $column
1133
	 * @return Filter\FilterDateRange
1134
	 * @throws DataGridException
1135
	 */
1136 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...
1137
	{
1138
		$column = $column ?: $key;
1139
1140
		if (!is_string($column)) {
1141
			throw new DataGridException("FilterDateRange can only filter in one column.");
1142
		}
1143
1144
		$this->addFilterCheck($key);
1145
1146
		return $this->filters[$key] = new Filter\FilterDateRange($this, $key, $name, $column, $name_second);
1147
	}
1148
1149
1150
	/**
1151
	 * Check whether given key already exists in $this->filters
1152
	 * @param  string $key
1153
	 * @throws DataGridException
1154
	 */
1155
	protected function addFilterCheck($key)
1156
	{
1157
		if (isset($this->filters[$key])) {
1158
			throw new DataGridException("There is already action at key [$key] defined.");
1159
		}
1160
	}
1161
1162
1163
	/**
1164
	 * Fill array of Filter\Filter[] with values from $this->filter persistent parameter
1165
	 * Fill array of Column\Column[] with values from $this->sort   persistent parameter
1166
	 * @return Filter\Filter[] $this->filters === Filter\Filter[]
1167
	 */
1168
	public function assableFilters()
1169
	{
1170
		foreach ($this->filter as $key => $value) {
1171
			if (!isset($this->filters[$key])) {
1172
				$this->deleteSesssionData($key);
1173
1174
				continue;
1175
			}
1176
1177
			if (is_array($value) || $value instanceof \Traversable) {
1178
				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...
1179
					$this->filters[$key]->setValue($value);
1180
				}
1181
			} else {
1182
				if ($value !== '' && $value !== NULL) {
1183
					$this->filters[$key]->setValue($value);
1184
				}
1185
			}
1186
		}
1187
1188
		foreach ($this->columns as $key => $column) {
1189
			if (isset($this->sort[$key])) {
1190
				$column->setSort($this->sort);
1191
			}
1192
		}
1193
1194
		return $this->filters;
1195
	}
1196
1197
1198
	/**
1199
	 * Remove filter
1200
	 * @param string $key
1201
	 * @return void
1202
	 */
1203
	public function removeFilter($key)
1204
	{
1205
		unset($this->filters[$key]);
1206
	}
1207
1208
1209
	/**
1210
	 * Get defined filter
1211
	 * @param  string $key
1212
	 * @return Filter\Filter
1213
	 */
1214
	public function getFilter($key)
1215
	{
1216
		if (!isset($this->filters[$key])) {
1217
			throw new DataGridException("Filter [{$key}] is not defined");
1218
		}
1219
1220
		return $this->filters[$key];
1221
	}
1222
1223
1224
	/********************************************************************************
1225
	 *                                  FILTERING                                   *
1226
	 ********************************************************************************/
1227
1228
1229
	/**
1230
	 * Is filter active?
1231
	 * @return boolean
1232
	 */
1233
	public function isFilterActive()
1234
	{
1235
		$is_filter = ArraysHelper::testTruthy($this->filter);
1236
1237
		return ($is_filter) || $this->force_filter_active;
1238
	}
1239
1240
1241
	/**
1242
	 * Tell that filter is active from whatever reasons
1243
	 * return static
1244
	 */
1245
	public function setFilterActive()
1246
	{
1247
		$this->force_filter_active = TRUE;
1248
1249
		return $this;
1250
	}
1251
1252
1253
	/**
1254
	 * Set filter values (force - overwrite user data)
1255
	 * @param array $filter
1256
	 * @return static
1257
	 */
1258
	public function setFilter(array $filter)
1259
	{
1260
		$this->filter = $filter;
1261
1262
		$this->saveSessionData('_grid_has_filtered', 1);
1263
1264
		return $this;
1265
	}
1266
1267
1268
	/**
1269
	 * If we want to sent some initial filter
1270
	 * @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...
1271
	 * @param bool  $use_on_reset
1272
	 * @return static
1273
	 */
1274
	public function setDefaultFilter(array $default_filter, $use_on_reset = TRUE)
1275
	{
1276
		foreach ($default_filter as $key => $value) {
1277
			$filter = $this->getFilter($key);
1278
1279
			if (!$filter) {
1280
				throw new DataGridException("Can not set default value to nonexisting filter [$key]");
1281
			}
1282
1283
			if ($filter instanceof Filter\FilterMultiSelect && !is_array($value)) {
1284
				throw new DataGridException(
1285
					"Default value of filter [$key] - MultiSelect has to be an array"
1286
				);
1287
			}
1288
1289
			if ($filter instanceof Filter\FilterRange || $filter instanceof Filter\FilterDateRange) {
1290
				if (!is_array($value)) {
1291
					throw new DataGridException(
1292
						"Default value of filter [$key] - Range/DateRange has to be an array [from/to => ...]"
1293
					);
1294
				}
1295
1296
				$temp = $value;
1297
				unset($temp['from'], $temp['to']);
1298
1299
				if (!empty($temp)) {
1300
					throw new DataGridException(
1301
						"Default value of filter [$key] - Range/DateRange can contain only [from/to => ...] values"
1302
					);
1303
				}
1304
			}
1305
		}
1306
1307
		$this->default_filter = $default_filter;
1308
		$this->default_filter_use_on_reset = (bool) $use_on_reset;
1309
1310
		return $this;
1311
	}
1312
1313
1314
	/**
1315
	 * User may set default filter, find it
1316
	 * @return void
1317
	 */
1318
	public function findDefaultFilter()
1319
	{
1320
		if (!empty($this->filter)) {
1321
			return;
1322
		}
1323
1324
		if ($this->getSessionData('_grid_has_filtered')) {
1325
			return;
1326
		}
1327
1328
		if (!empty($this->default_filter)) {
1329
			$this->filter = $this->default_filter;
1330
		}
1331
1332
		foreach ($this->filter as $key => $value) {
1333
			$this->saveSessionData($key, $value);
1334
		}
1335
	}
1336
1337
1338
	/**
1339
	 * FilterAndGroupAction form factory
1340
	 * @return Form
1341
	 */
1342
	public function createComponentFilter()
1343
	{
1344
		$form = new Form($this, 'filter');
1345
1346
		$form->setMethod(static::$form_method);
1347
1348
		$form->setTranslator($this->getTranslator());
1349
1350
		/**
1351
		 * InlineEdit part
1352
		 */
1353
		$inline_edit_container = $form->addContainer('inline_edit');
1354
1355 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...
1356
			$inline_edit_container->addSubmit('submit', 'ublaboo_datagrid.save');
1357
			$inline_edit_container->addSubmit('cancel', 'ublaboo_datagrid.cancel')
1358
				->setValidationScope(FALSE);
1359
1360
			$this->inlineEdit->onControlAdd($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
		}
1376
1377
		/**
1378
		 * ItemDetail form part
1379
		 */
1380
		$items_detail_form = $this->getItemDetailForm();
1381
1382
		if ($items_detail_form instanceof Nette\Forms\Container) {
1383
			$form['items_detail_form'] = $items_detail_form;
1384
		}
1385
1386
		/**
1387
		 * Filter part
1388
		 */
1389
		$filter_container = $form->addContainer('filter');
1390
1391
		foreach ($this->filters as $filter) {
1392
			$filter->addToFormContainer($filter_container);
1393
		}
1394
1395
		if (!$this->hasAutoSubmit()) {
1396
			$filter_container['submit'] = $this->getFilterSubmitButton();
1397
		}
1398
1399
		/**
1400
		 * Group action part
1401
		 */
1402
		$group_action_container = $form->addContainer('group_action');
1403
1404
		if ($this->hasGroupActions()) {
1405
			$this->getGroupActionCollection()->addToFormContainer($group_action_container);
1406
		}
1407
1408
		$form->setDefaults(['filter' => $this->filter]);
1409
1410
		/**
1411
		 * Per page part
1412
		 */
1413
		$form->addSelect('per_page', '', $this->getItemsPerPageList())
1414
			->setTranslator(NULL);
1415
1416
		if (!$form->isSubmitted()) {
1417
			$form['per_page']->setValue($this->getPerPage());
1418
		}
1419
1420
		$form->addSubmit('per_page_submit', '');
1421
		
1422
		$form->onSubmit[] = [$this, 'filterSucceeded'];
1423
1424
		return $form;
1425
	}
1426
1427
1428
	/**
1429
	 * Set $this->filter values after filter form submitted
1430
	 * @param  Form $form
1431
	 * @return void
1432
	 */
1433
	public function filterSucceeded(Form $form)
1434
	{
1435
		if ($this->snippets_set) {
1436
			return;
1437
		}
1438
1439
		$values = $form->getValues();
1440
1441
		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...
1442
			if (isset($form['group_action']['submit']) && $form['group_action']['submit']->isSubmittedBy()) {
1443
				return;
1444
			}
1445
		}
1446
1447
		/**
1448
		 * Per page
1449
		 */
1450
		$this->saveSessionData('_grid_per_page', $values->per_page);
1451
		$this->per_page = $values->per_page;
1452
1453
		/**
1454
		 * Inline edit
1455
		 */
1456
		if (isset($form['inline_edit']) && isset($form['inline_edit']['submit']) && isset($form['inline_edit']['cancel'])) {
1457
			$edit = $form['inline_edit'];
1458
1459
			if ($edit['submit']->isSubmittedBy() || $edit['cancel']->isSubmittedBy()) {
1460
				$id = $form->getHttpData(Form::DATA_LINE, 'inline_edit[_id]');
1461
				$primary_where_column = $form->getHttpData(
1462
					Form::DATA_LINE,
1463
					'inline_edit[_primary_where_column]'
1464
				);
1465
1466
				if ($edit['submit']->isSubmittedBy()) {
1467
					$this->inlineEdit->onSubmit($id, $values->inline_edit);
1468
					$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...
1469
				} else {
1470
					$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...
1471
				}
1472
1473
				if ($edit['submit']->isSubmittedBy() && !empty($this->inlineEdit->onCustomRedraw)) {
1474
					$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...
1475
				} else {
1476
					$this->redrawItem($id, $primary_where_column);
1477
				}
1478
1479
				return;
1480
			}
1481
		}
1482
1483
		/**
1484
		 * Inline add
1485
		 */
1486
		if (isset($form['inline_add']) && isset($form['inline_add']['submit']) && isset($form['inline_add']['cancel'])) {
1487
			$add = $form['inline_add'];
1488
1489
			if ($add['submit']->isSubmittedBy() || $add['cancel']->isSubmittedBy()) {
1490
				if ($add['submit']->isSubmittedBy()) {
1491
					$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...
1492
1493
					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...
1494
						$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...
1495
					}
1496
				}
1497
1498
				return;
1499
			}
1500
		}
1501
1502
		/**
1503
		 * Filter itself
1504
		 */
1505
		$values = $values['filter'];
1506
1507
		foreach ($values as $key => $value) {
1508
			/**
1509
			 * Session stuff
1510
			 */
1511
			$this->saveSessionData($key, $value);
1512
1513
			/**
1514
			 * Other stuff
1515
			 */
1516
			$this->filter[$key] = $value;
1517
		}
1518
1519
		if (!empty($values)) {
1520
			$this->saveSessionData('_grid_has_filtered', 1);
1521
		}
1522
1523
		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...
1524
			$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...
1525
1526
			foreach ($this->columns as $key => $column) {
1527
				if ($column->isSortable()) {
1528
					$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...
1529
						'sort' => $column->getSortNext()
1530
					]);
1531
				}
1532
			}
1533
		}
1534
1535
		$this->reload();
1536
	}
1537
1538
1539
	/**
1540
	 * Should be datagrid filters rendered separately?
1541
	 * @param boolean $out
1542
	 * @return static
1543
	 */
1544
	public function setOuterFilterRendering($out = TRUE)
1545
	{
1546
		$this->outer_filter_rendering = (bool) $out;
1547
1548
		return $this;
1549
	}
1550
1551
1552
	/**
1553
	 * Are datagrid filters rendered separately?
1554
	 * @return boolean
1555
	 */
1556
	public function hasOuterFilterRendering()
1557
	{
1558
		return $this->outer_filter_rendering;
1559
	}
1560
1561
1562
	/**
1563
	 * Try to restore session stuff
1564
	 * @return void
1565
	 */
1566
	public function findSessionValues()
1567
	{
1568
		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...
1569
			return;
1570
		}
1571
1572
		if (!$this->remember_state) {
1573
			return;
1574
		}
1575
1576
		if ($page = $this->getSessionData('_grid_page')) {
1577
			$this->page = $page;
1578
		}
1579
1580
		if ($per_page = $this->getSessionData('_grid_per_page')) {
1581
			$this->per_page = $per_page;
1582
		}
1583
1584
		if ($sort = $this->getSessionData('_grid_sort')) {
1585
			$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...
1586
		}
1587
1588
		foreach ($this->getSessionData() as $key => $value) {
1589
			$other_session_keys = [
1590
				'_grid_per_page',
1591
				'_grid_sort',
1592
				'_grid_page',
1593
				'_grid_has_filtered',
1594
				'_grid_hidden_columns',
1595
				'_grid_hidden_columns_manipulated'
1596
			];
1597
1598
			if (!in_array($key, $other_session_keys)) {
1599
				$this->filter[$key] = $value;
1600
			}
1601
		}
1602
1603
		/**
1604
		 * When column is sorted via custom callback, apply it
1605
		 */
1606
		if (empty($this->sort_callback) && !empty($this->sort)) {
1607
			foreach ($this->sort as $key => $order) {
1608
				$column = $this->getColumn($key);
1609
1610
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1611
					$this->sort_callback = $column->getSortableCallback();
1612
				}
1613
			}
1614
		}
1615
	}
1616
1617
1618
	/********************************************************************************
1619
	 *                                    EXPORTS                                   *
1620
	 ********************************************************************************/
1621
1622
1623
	/**
1624
	 * Add export of type callback
1625
	 * @param string $text
1626
	 * @param callable $callback
1627
	 * @param boolean $filtered
1628
	 * @return Export\Export
1629
	 */
1630
	public function addExportCallback($text, $callback, $filtered = FALSE)
1631
	{
1632
		if (!is_callable($callback)) {
1633
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1634
		}
1635
1636
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1637
	}
1638
1639
1640
	/**
1641
	 * Add already implemented csv export
1642
	 * @param string $text
1643
	 * @param string $csv_file_name
1644
	 * @param string|null $output_encoding
1645
	 * @param string|null $delimiter
1646
	 * @param bool $include_bom
1647
	 * @return Export\Export
1648
	 */
1649 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...
1650
	{
1651
		return $this->addToExports(new Export\ExportCsv(
1652
			$this,
1653
			$text,
1654
			$csv_file_name,
1655
			FALSE,
1656
			$output_encoding,
1657
			$delimiter,
1658
			$include_bom
1659
		));
1660
	}
1661
1662
1663
	/**
1664
	 * Add already implemented csv export, but for filtered data
1665
	 * @param string $text
1666
	 * @param string $csv_file_name
1667
	 * @param string|null $output_encoding
1668
	 * @param string|null $delimiter
1669
	 * @param bool $include_bom
1670
	 * @return Export\Export
1671
	 */
1672 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...
1673
	{
1674
		return $this->addToExports(new Export\ExportCsv(
1675
			$this,
1676
			$text,
1677
			$csv_file_name,
1678
			TRUE,
1679
			$output_encoding,
1680
			$delimiter,
1681
			$include_bom
1682
		));
1683
	}
1684
1685
1686
	/**
1687
	 * Add export to array
1688
	 * @param Export\Export $export
1689
	 * @return Export\Export
1690
	 */
1691
	protected function addToExports(Export\Export $export)
1692
	{
1693
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1694
1695
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1696
1697
		return $this->exports[$id] = $export;
1698
	}
1699
1700
1701
	public function resetExportsLinks()
1702
	{
1703
		foreach ($this->exports as $id => $export) {
1704
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1705
		}
1706
	}
1707
1708
1709
	/********************************************************************************
1710
	 *                                TOOLBAR BUTTONS                               *
1711
	 ********************************************************************************/
1712
1713
1714
	/**
1715
	 * Add toolbar button
1716
	 * @param string $href
1717
	 * @param string $text
1718
	 * @param array  $params
1719
	 * @return ToolbarButton
1720
	 */
1721
	public function addToolbarButton($href, $text = '', $params = [])
1722
	{
1723
		$button = new ToolbarButton($this, $href, $text, $params);
1724
1725
		return $this->toolbar_buttons[] = $button;
1726
	}
1727
1728
1729
	/********************************************************************************
1730
	 *                                 GROUP ACTIONS                                *
1731
	 ********************************************************************************/
1732
1733
1734
	/**
1735
	 * Alias for add group select action
1736
	 * @param string $title
1737
	 * @param array  $options
1738
	 * @return GroupAction\GroupAction
1739
	 */
1740
	public function addGroupAction($title, $options = [])
1741
	{
1742
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1743
	}
1744
1745
	/**
1746
	 * Add group action (select box)
1747
	 * @param string $title
1748
	 * @param array  $options
1749
	 * @return GroupAction\GroupAction
1750
	 */
1751
	public function addGroupSelectAction($title, $options = [])
1752
	{
1753
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1754
	}
1755
1756
	/**
1757
	 * Add group action (text input)
1758
	 * @param string $title
1759
	 * @return GroupAction\GroupAction
1760
	 */
1761
	public function addGroupTextAction($title)
1762
	{
1763
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1764
	}
1765
1766
	/**
1767
	 * Get collection of all group actions
1768
	 * @return GroupAction\GroupActionCollection
1769
	 */
1770
	public function getGroupActionCollection()
1771
	{
1772
		if (!$this->group_action_collection) {
1773
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1774
		}
1775
1776
		return $this->group_action_collection;
1777
	}
1778
1779
1780
	/**
1781
	 * Has datagrid some group actions?
1782
	 * @return boolean
1783
	 */
1784
	public function hasGroupActions()
1785
	{
1786
		return (bool) $this->group_action_collection;
1787
	}
1788
1789
1790
	/********************************************************************************
1791
	 *                                   HANDLERS                                   *
1792
	 ********************************************************************************/
1793
1794
1795
	/**
1796
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1797
	 * @param  int  $page
1798
	 * @return void
1799
	 */
1800
	public function handlePage($page)
1801
	{
1802
		/**
1803
		 * Session stuff
1804
		 */
1805
		$this->page = $page;
1806
		$this->saveSessionData('_grid_page', $page);
1807
1808
		$this->reload(['table']);
1809
	}
1810
1811
1812
	/**
1813
	 * Handler for sorting
1814
	 * @param array $sort
1815
	 * @return void
1816
	 */
1817
	public function handleSort(array $sort)
1818
	{
1819
		$new_sort = [];
1820
1821
		/**
1822
		 * Find apropirate column
1823
		 */
1824
		foreach ($sort as $key => $value) {
1825
			if (empty($this->columns[$key])) {
1826
				throw new DataGridException("Column <$key> not found");
1827
			}
1828
1829
			$column = $this->columns[$key];
1830
			$new_sort = [$key => $value];
1831
1832
			/**
1833
			 * Pagination may be reseted after sorting
1834
			 */
1835
			if ($column->sortableResetPagination()) {
1836
				$this->page = 1;
1837
				$this->saveSessionData('_grid_page', 1);
1838
			}
1839
1840
			/**
1841
			 * Custom sorting callback may be applied
1842
			 */
1843
			if ($column->getSortableCallback()) {
1844
				$this->sort_callback = $column->getSortableCallback();
1845
			}
1846
		}
1847
1848
		/**
1849
		 * Session stuff
1850
		 */
1851
		$this->sort = $new_sort;
1852
		$this->saveSessionData('_grid_sort', $this->sort);
1853
1854
		$this->reload(['table']);
1855
	}
1856
1857
1858
	/**
1859
	 * handler for reseting the filter
1860
	 * @return void
1861
	 */
1862
	public function handleResetFilter()
1863
	{
1864
		/**
1865
		 * Session stuff
1866
		 */
1867
		$this->deleteSesssionData('_grid_page');
1868
1869
		if ($this->default_filter_use_on_reset) {
1870
			$this->deleteSesssionData('_grid_has_filtered');
1871
		}
1872
1873
		foreach ($this->getSessionData() as $key => $value) {
1874
			if (!in_array($key, ['_grid_per_page', '_grid_sort', '_grid_page', '_grid_has_filtered'])) {
1875
				$this->deleteSesssionData($key);
1876
			}
1877
		}
1878
1879
		$this->filter = [];
1880
1881
		$this->reload(['grid']);
1882
	}
1883
1884
1885
	/**
1886
	 * Handler for export
1887
	 * @param  int $id Key for particular export class in array $this->exports
1888
	 * @return void
1889
	 */
1890
	public function handleExport($id)
1891
	{
1892
		if (!isset($this->exports[$id])) {
1893
			throw new Nette\Application\ForbiddenRequestException;
1894
		}
1895
1896
		if (!empty($this->columns_export_order)) {
1897
			$this->setColumnsOrder($this->columns_export_order);
1898
		}
1899
1900
		$export = $this->exports[$id];
1901
1902
		if ($export->isFiltered()) {
1903
			$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...
1904
			$filter    = $this->assableFilters();
1905
		} else {
1906
			$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...
1907
			$filter    = [];
1908
		}
1909
1910
		if (NULL === $this->dataModel) {
1911
			throw new DataGridException('You have to set a data source first.');
1912
		}
1913
1914
		$rows = [];
1915
1916
		$items = Nette\Utils\Callback::invokeArgs(
1917
			[$this->dataModel, 'filterData'], [
1918
				NULL,
1919
				$this->createSorting($this->sort, $this->sort_callback),
1920
				$filter
1921
			]
1922
		);
1923
1924
		foreach ($items as $item) {
1925
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1926
		}
1927
1928
		if ($export instanceof Export\ExportCsv) {
1929
			$export->invoke($rows);
1930
		} else {
1931
			$export->invoke($items);
1932
		}
1933
1934
		if ($export->isAjax()) {
1935
			$this->reload();
1936
		}
1937
	}
1938
1939
1940
	/**
1941
	 * Handler for getting children of parent item (e.g. category)
1942
	 * @param  int $parent
1943
	 * @return void
1944
	 */
1945
	public function handleGetChildren($parent)
1946
	{
1947
		$this->setDataSource(
1948
			call_user_func($this->tree_view_children_callback, $parent)
1949
		);
1950
1951
		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...
1952
			$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...
1953
			$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...
1954
1955
			$this->redrawControl('items');
1956
1957
			$this->onRedraw();
1958
		} else {
1959
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1960
		}
1961
	}
1962
1963
1964
	/**
1965
	 * Handler for getting item detail
1966
	 * @param  mixed $id
1967
	 * @return void
1968
	 */
1969
	public function handleGetItemDetail($id)
1970
	{
1971
		$this->getTemplate()->add('toggle_detail', $id);
1972
		$this->redraw_item = [$this->items_detail->getPrimaryWhereColumn() => $id];
1973
1974
		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...
1975
			$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...
1976
			$this->redrawControl('items');
1977
1978
			$this->onRedraw();
1979
		} else {
1980
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
1981
		}
1982
	}
1983
1984
1985
	/**
1986
	 * Handler for inline editing
1987
	 * @param  mixed $id
1988
	 * @param  mixed $key
1989
	 * @return void
1990
	 */
1991
	public function handleEdit($id, $key)
1992
	{
1993
		$column = $this->getColumn($key);
1994
		$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...
1995
1996
		call_user_func_array($column->getEditableCallback(), [$id, $value]);
1997
	}
1998
1999
2000
	/**
2001
	 * Redraw $this
2002
	 * @return void
2003
	 */
2004
	public function reload($snippets = [])
2005
	{
2006
		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...
2007
			$this->redrawControl('tbody');
2008
			$this->redrawControl('pagination');
2009
2010
			/**
2011
			 * manualy reset exports links...
2012
			 */
2013
			$this->resetExportsLinks();
2014
			$this->redrawControl('exports');
2015
2016
			foreach ($snippets as $snippet) {
2017
				$this->redrawControl($snippet);
2018
			}
2019
2020
			$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...
2021
2022
			$this->onRedraw();
2023
		} else {
2024
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Nette\Application\UI\PresenterComponent, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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