Completed
Push — master ( f463c7...38c89a )
by Pavel
03:01
created

DataGrid::addGroupTextareaAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/**
4
 * @copyright   Copyright (c) 2015 ublaboo <[email protected]>
5
 * @author      Pavel Janda <[email protected]>
6
 * @package     Ublaboo
7
 */
8
9
namespace Ublaboo\DataGrid;
10
11
use Nette;
12
use Nette\Application\UI\Link;
13
use Nette\Application\UI\PresenterComponent;
14
use Ublaboo\DataGrid\Utils\ArraysHelper;
15
use Nette\Application\UI\Form;
16
use Ublaboo\DataGrid\Exception\DataGridException;
17
use Ublaboo\DataGrid\Exception\DataGridHasToBeAttachedToPresenterComponentException;
18
use Ublaboo\DataGrid\Utils\Sorting;
19
use Ublaboo\DataGrid\InlineEdit\InlineEdit;
20
use Ublaboo\DataGrid\ColumnsSummary;
21
use Ublaboo\DataGrid\Toolbar\ToolbarButton;
22
23
/**
24
 * @method onRedraw()
25
 * @method onRender()
26
 * @method onColumnAdd()
27
 */
28
class DataGrid extends Nette\Application\UI\Control
29
{
30
31
	/**
32
	 * @var callable[]
33
	 */
34
	public $onRedraw;
35
36
	/**
37
	 * @var callable[]
38
	 */
39
	public $onRender = [];
40
41
	/**
42
	 * @var callable[]
43
	 */
44
	public $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
				try {
1609
					$column = $this->getColumn($key);
1610
1611
				} catch (DataGridException $e) {
1612
					$this->deleteSesssionData('_grid_sort');
1613
					$this->sort = [];
1614
1615
					return;
1616
				}
1617
1618
				if ($column && $column->isSortable() && is_callable($column->getSortableCallback())) {
1619
					$this->sort_callback = $column->getSortableCallback();
1620
				}
1621
			}
1622
		}
1623
	}
1624
1625
1626
	/********************************************************************************
1627
	 *                                    EXPORTS                                   *
1628
	 ********************************************************************************/
1629
1630
1631
	/**
1632
	 * Add export of type callback
1633
	 * @param string $text
1634
	 * @param callable $callback
1635
	 * @param boolean $filtered
1636
	 * @return Export\Export
1637
	 */
1638
	public function addExportCallback($text, $callback, $filtered = FALSE)
1639
	{
1640
		if (!is_callable($callback)) {
1641
			throw new DataGridException("Second parameter of ExportCallback must be callable.");
1642
		}
1643
1644
		return $this->addToExports(new Export\Export($this, $text, $callback, $filtered));
1645
	}
1646
1647
1648
	/**
1649
	 * Add already implemented csv export
1650
	 * @param string $text
1651
	 * @param string $csv_file_name
1652
	 * @param string|null $output_encoding
1653
	 * @param string|null $delimiter
1654
	 * @param bool $include_bom
1655
	 * @return Export\Export
1656
	 */
1657 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...
1658
	{
1659
		return $this->addToExports(new Export\ExportCsv(
1660
			$this,
1661
			$text,
1662
			$csv_file_name,
1663
			FALSE,
1664
			$output_encoding,
1665
			$delimiter,
1666
			$include_bom
1667
		));
1668
	}
1669
1670
1671
	/**
1672
	 * Add already implemented csv export, but for filtered data
1673
	 * @param string $text
1674
	 * @param string $csv_file_name
1675
	 * @param string|null $output_encoding
1676
	 * @param string|null $delimiter
1677
	 * @param bool $include_bom
1678
	 * @return Export\Export
1679
	 */
1680 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...
1681
	{
1682
		return $this->addToExports(new Export\ExportCsv(
1683
			$this,
1684
			$text,
1685
			$csv_file_name,
1686
			TRUE,
1687
			$output_encoding,
1688
			$delimiter,
1689
			$include_bom
1690
		));
1691
	}
1692
1693
1694
	/**
1695
	 * Add export to array
1696
	 * @param Export\Export $export
1697
	 * @return Export\Export
1698
	 */
1699
	protected function addToExports(Export\Export $export)
1700
	{
1701
		$id = ($s = sizeof($this->exports)) ? ($s + 1) : 1;
1702
1703
		$export->setLink(new Link($this, 'export!', ['id' => $id]));
1704
1705
		return $this->exports[$id] = $export;
1706
	}
1707
1708
1709
	public function resetExportsLinks()
1710
	{
1711
		foreach ($this->exports as $id => $export) {
1712
			$export->setLink(new Link($this, 'export!', ['id' => $id]));
1713
		}
1714
	}
1715
1716
1717
	/********************************************************************************
1718
	 *                                TOOLBAR BUTTONS                               *
1719
	 ********************************************************************************/
1720
1721
1722
	/**
1723
	 * Add toolbar button
1724
	 * @param string $href
1725
	 * @param string $text
1726
	 * @param array  $params
1727
	 * @return ToolbarButton
1728
	 */
1729
	public function addToolbarButton($href, $text = '', $params = [])
1730
	{
1731
		$button = new ToolbarButton($this, $href, $text, $params);
1732
1733
		return $this->toolbar_buttons[] = $button;
1734
	}
1735
1736
1737
	/********************************************************************************
1738
	 *                                 GROUP ACTIONS                                *
1739
	 ********************************************************************************/
1740
1741
1742
	/**
1743
	 * Alias for add group select action
1744
	 * @param string $title
1745
	 * @param array  $options
1746
	 * @return GroupAction\GroupAction
1747
	 */
1748
	public function addGroupAction($title, $options = [])
1749
	{
1750
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1751
	}
1752
1753
1754
	/**
1755
	 * Add group action (select box)
1756
	 * @param string $title
1757
	 * @param array  $options
1758
	 * @return GroupAction\GroupAction
1759
	 */
1760
	public function addGroupSelectAction($title, $options = [])
1761
	{
1762
		return $this->getGroupActionCollection()->addGroupSelectAction($title, $options);
1763
	}
1764
1765
1766
	/**
1767
	 * Add group action (text input)
1768
	 * @param string $title
1769
	 * @return GroupAction\GroupAction
1770
	 */
1771
	public function addGroupTextAction($title)
1772
	{
1773
		return $this->getGroupActionCollection()->addGroupTextAction($title);
1774
	}
1775
1776
1777
	/**
1778
	 * Add group action (textarea)
1779
	 * @param string $title
1780
	 * @return GroupAction\GroupAction
1781
	 */
1782
	public function addGroupTextareaAction($title)
1783
	{
1784
		return $this->getGroupActionCollection()->addGroupTextareaAction($title);
1785
	}
1786
1787
1788
	/**
1789
	 * Get collection of all group actions
1790
	 * @return GroupAction\GroupActionCollection
1791
	 */
1792
	public function getGroupActionCollection()
1793
	{
1794
		if (!$this->group_action_collection) {
1795
			$this->group_action_collection = new GroupAction\GroupActionCollection($this);
1796
		}
1797
1798
		return $this->group_action_collection;
1799
	}
1800
1801
1802
	/**
1803
	 * Has datagrid some group actions?
1804
	 * @return boolean
1805
	 */
1806
	public function hasGroupActions()
1807
	{
1808
		return (bool) $this->group_action_collection;
1809
	}
1810
1811
1812
	/********************************************************************************
1813
	 *                                   HANDLERS                                   *
1814
	 ********************************************************************************/
1815
1816
1817
	/**
1818
	 * Handler for changind page (just refresh site with page as persistent paramter set)
1819
	 * @param  int  $page
1820
	 * @return void
1821
	 */
1822
	public function handlePage($page)
1823
	{
1824
		/**
1825
		 * Session stuff
1826
		 */
1827
		$this->page = $page;
1828
		$this->saveSessionData('_grid_page', $page);
1829
1830
		$this->reload(['table']);
1831
	}
1832
1833
1834
	/**
1835
	 * Handler for sorting
1836
	 * @param array $sort
1837
	 * @return void
1838
	 */
1839
	public function handleSort(array $sort)
1840
	{
1841
		$new_sort = [];
1842
1843
		/**
1844
		 * Find apropirate column
1845
		 */
1846
		foreach ($sort as $key => $value) {
1847
			if (empty($this->columns[$key])) {
1848
				throw new DataGridException("Column <$key> not found");
1849
			}
1850
1851
			$column = $this->columns[$key];
1852
			$new_sort = [$key => $value];
1853
1854
			/**
1855
			 * Pagination may be reseted after sorting
1856
			 */
1857
			if ($column->sortableResetPagination()) {
1858
				$this->page = 1;
1859
				$this->saveSessionData('_grid_page', 1);
1860
			}
1861
1862
			/**
1863
			 * Custom sorting callback may be applied
1864
			 */
1865
			if ($column->getSortableCallback()) {
1866
				$this->sort_callback = $column->getSortableCallback();
1867
			}
1868
		}
1869
1870
		/**
1871
		 * Session stuff
1872
		 */
1873
		$this->sort = $new_sort;
1874
		$this->saveSessionData('_grid_sort', $this->sort);
1875
1876
		$this->reload(['table']);
1877
	}
1878
1879
1880
	/**
1881
	 * handler for reseting the filter
1882
	 * @return void
1883
	 */
1884
	public function handleResetFilter()
1885
	{
1886
		/**
1887
		 * Session stuff
1888
		 */
1889
		$this->deleteSesssionData('_grid_page');
1890
1891
		if ($this->default_filter_use_on_reset) {
1892
			$this->deleteSesssionData('_grid_has_filtered');
1893
		}
1894
1895
		foreach ($this->getSessionData() as $key => $value) {
1896
			if (!in_array($key, [
1897
				'_grid_per_page',
1898
				'_grid_sort',
1899
				'_grid_page',
1900
				'_grid_has_filtered',
1901
				'_grid_hidden_columns',
1902
				'_grid_hidden_columns_manipulated'
1903
				])) {
1904
1905
				$this->deleteSesssionData($key);
1906
			}
1907
		}
1908
1909
		$this->filter = [];
1910
1911
		$this->reload(['grid']);
1912
	}
1913
1914
1915
	/**
1916
	 * Handler for export
1917
	 * @param  int $id Key for particular export class in array $this->exports
1918
	 * @return void
1919
	 */
1920
	public function handleExport($id)
1921
	{
1922
		if (!isset($this->exports[$id])) {
1923
			throw new Nette\Application\ForbiddenRequestException;
1924
		}
1925
1926
		if (!empty($this->columns_export_order)) {
1927
			$this->setColumnsOrder($this->columns_export_order);
1928
		}
1929
1930
		$export = $this->exports[$id];
1931
1932
		if ($export->isFiltered()) {
1933
			$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...
1934
			$filter    = $this->assableFilters();
1935
		} else {
1936
			$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...
1937
			$filter    = [];
1938
		}
1939
1940
		if (NULL === $this->dataModel) {
1941
			throw new DataGridException('You have to set a data source first.');
1942
		}
1943
1944
		$rows = [];
1945
1946
		$items = Nette\Utils\Callback::invokeArgs(
1947
			[$this->dataModel, 'filterData'], [
1948
				NULL,
1949
				$this->createSorting($this->sort, $this->sort_callback),
1950
				$filter
1951
			]
1952
		);
1953
1954
		foreach ($items as $item) {
1955
			$rows[] = new Row($this, $item, $this->getPrimaryKey());
1956
		}
1957
1958
		if ($export instanceof Export\ExportCsv) {
1959
			$export->invoke($rows);
1960
		} else {
1961
			$export->invoke($items);
1962
		}
1963
1964
		if ($export->isAjax()) {
1965
			$this->reload();
1966
		}
1967
	}
1968
1969
1970
	/**
1971
	 * Handler for getting children of parent item (e.g. category)
1972
	 * @param  int $parent
1973
	 * @return void
1974
	 */
1975
	public function handleGetChildren($parent)
1976
	{
1977
		$this->setDataSource(
1978
			call_user_func($this->tree_view_children_callback, $parent)
1979
		);
1980
1981
		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...
1982
			$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...
1983
			$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...
1984
1985
			$this->redrawControl('items');
1986
1987
			$this->onRedraw();
1988
		} else {
1989
			$this->getPresenter()->redirect('this');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Nette\ComponentModel\IComponent as the method redirect() does only exist in the following implementations of said interface: Nette\Application\UI\Component, Nette\Application\UI\Control, Nette\Application\UI\Multiplier, Nette\Application\UI\Presenter, Ublaboo\DataGrid\Compone...nator\DataGridPaginator, Ublaboo\DataGrid\DataGrid.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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

This error could be the result of:

1. Missing dependencies

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

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

2. Missing use statement

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

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

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

Loading history...
2946
			throw new DataGridHasToBeAttachedToPresenterComponentException(
2947
				"DataGrid is attached to: '" . get_class($parent) . "', but instance of PresenterComponent is needed."
2948
			);
2949
		}
2950
2951
		return $parent;
2952
	}
2953
2954
2955
	/**
2956
	 * Some of datagrid columns is hidden by default
2957
	 * @param bool $default_hide
2958
	 */
2959
	public function setSomeColumnDefaultHide($default_hide)
2960
	{
2961
		$this->some_column_default_hide = $default_hide;
2962
	}
2963
2964
2965
	/**
2966
	 * Are some of columns hidden bydefault?
2967
	 */
2968
	public function hasSomeColumnDefaultHide()
2969
	{
2970
		return $this->some_column_default_hide;
2971
	}
2972
2973
}
2974